summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/issues/_filter_favourites2.html.erb22
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/issues/search2.html.erb102
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb6
-rw-r--r--sonar-server/src/main/webapp/javascripts/navigator/app.js89
-rw-r--r--sonar-server/src/main/webapp/javascripts/navigator/filters.js289
-rw-r--r--sonar-server/src/main/webapp/javascripts/navigator/filters/ajax-select-filters.js260
-rw-r--r--sonar-server/src/main/webapp/javascripts/navigator/filters/base-filters.js228
-rw-r--r--sonar-server/src/main/webapp/javascripts/navigator/filters/favorite-filters.js63
-rw-r--r--sonar-server/src/main/webapp/javascripts/navigator/filters/range-filters.js99
-rw-r--r--sonar-server/src/main/webapp/javascripts/navigator/filters/select-filters.js66
-rw-r--r--sonar-server/src/main/webapp/stylesheets/navigator.css136
-rw-r--r--sonar-server/src/main/webapp/stylesheets/navigator.less155
-rw-r--r--sonar-server/src/main/webapp/stylesheets/variables.css3
-rw-r--r--sonar-server/src/main/webapp/stylesheets/variables.less8
-rw-r--r--sonar-server/wro.xml6
15 files changed, 1068 insertions, 464 deletions
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_filter_favourites2.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_filter_favourites2.html.erb
index dc1bd72a906..4824fa99474 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_filter_favourites2.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_filter_favourites2.html.erb
@@ -1,21 +1,7 @@
<% if logged_in? %>
-<div style="border-bottom: 1px solid #ccc; padding-bottom: 10px;">
- <ul>
- <% if !@favourite_filters.empty? %>
- <% @favourite_filters.each do |filter| %>
- <li>
- <a href="<%= ApplicationController.root_context -%>/issues/filter/<%= filter.id -%>"><%= h filter.name -%></a>
- </li>
- <% end %>
- <% else %>
- <li>No favorite filters</li>
+ {
+ <% @favourite_filters.each do |filter| %>
+ '<%= filter.id -%>': '<%= h filter.name -%>',
<% end %>
- </ul>
-</div>
-
-<div>
- <ul>
- <li><a href="<%= ApplicationController.root_context -%>/issues/manage" class="link-action"><%= message('manage') %></a></li>
- </ul>
-</div>
+ }
<% end %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search2.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search2.html.erb
index 5a79e409b2f..daef4f4f011 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search2.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search2.html.erb
@@ -18,45 +18,92 @@
}
</script>
-<script id="filterTemplate" type="text/template">
+<script id="baseFilterTemplate" type="text/template">
<div class="navigator-filter-label">{{ name }}</div>
- <div class="navigator-filter-body">{{ body }}</div>
+ <div class="navigator-filter-value {[ if (defaultValue) { ]}default{[ } ]}">{{ value }}</div>
+</script>
+
+<script id="detailsFilterTemplate" type="text/template">
+ <div class="navigator-filter-details-inner">
+ No details
+ </div>
</script>
<script id="filterBarTemplate" type="text/template">
- <form action="<%= ApplicationController.root_context -%>/issues/search" method="get">
- <div class="navigator-filters-favorite">
- <a class="navigator-filters-favorite-toggle" onclick="showDropdownMenu('favorite-filters'); return false;"></a>
-
- <div id="favorite-filters" class="dropdown-menu" style="max-width: none; display: none; font-size: 12px;">
- <%= render :partial => 'issues/filter_favourites2' -%>
- </div>
- </div>
-
- <div class="navigator-filters-list"></div>
- <div class="navigator-filters-actions">
- <select class="navigator-disabled-filters"></select>
- </div>
- </form>
+ <div class="navigator-filters-list"></div>
</script>
<script id="selectFilterTemplate" type="text/template">
- <select multiple="multiple" name="{{ property }}">
- {[ _.each(choices, function(choice) { ]}
- <option value="{[ print(choice[1]); ]}">{[ print(choice[0]); ]}</option>
+ <ul class="navigator-filter-select-list">
+ {[ _.each(choices, function(value, key) { ]}
+ <li>
+ <label>
+ <input type="checkbox" name="{{ property }}" value="{{ key }}">
+ <span>{{ value }}</span>
+ </label>
+ </li>
+ {[ }); ]}
+ </ul>
+</script>
+
+<script id="projectFilterTemplate" type="text/template">
+ <div class="navigator-filter-search">
+ <input type="text">
+ </div>
+</script>
+
+<script id="projectSuggestionsFilterTemplate" type="text/template">
+ {[ if (suggestions.length > 0) { ]}
+ {[ _.each(suggestions, function(s) { ]}
+ <li>
+ <label>
+ <input type="checkbox" name="{{ property }}" value="{{ s.id }}">
+ <span>{{ s.text }}</span>
+ </label>
+ </li>
{[ }); ]}
- </select>
+ {[ } else { ]}
+ <li><label>Start typing to get results</label></li>
+ {[ } ]}
</script>
-<script id="ajaxSelectFilterTemplate" type="text/template">
- <input type="hidden" name="{{ property }}">
+<script id="projectSuggestionTemplate" type="text/template">
+ <label>
+ <input type="checkbox" value="{{ id }}" {[ if (selected) { ]}checked{[ } ]}>
+ <span>{{ text }}</span>
+ </label>
+</script>
+
+<script id="projectNoSuggestionsTemplate" type="text/template">
+ No results
</script>
<script id="rangeFilterTemplate" type="text/template">
- <input class="navigator-filter-range-input" type="text"
- name="{{ propertyFrom }}" placeholder="From">
- <input class="navigator-filter-range-input" type="text"
- name="{{ propertyTo }}" placeholder="To">
+ <div class="navigator-filter-details-inner">
+ <input class="navigator-filter-range-input" type="text"
+ name="{{ propertyFrom }}">
+ <label>to</label>
+ <input class="navigator-filter-range-input" type="text"
+ name="{{ propertyTo }}">
+ </div>
+</script>
+
+<script id="favoriteFilterTemplate" type="text/template">
+ <div class="navigator-filter-favorite"></div>
+</script>
+
+<script id="detailsFavoriteFilterTemplate" type="text/template">
+ <ul class="navigator-filter-select-list">
+ {[ _.each(choices, function(value, key) { ]}
+ <li>
+ <label data-id="{{ key }}">{{ value }}</label>
+ </li>
+ {[ }); ]}
+ <li class="line"></li>
+ <li class="manage">
+ <label>Manage</label>
+ </li>
+ </ul>
</script>
<script>
@@ -68,7 +115,8 @@
_.extend(window.SS, {
severities: <%= RulesConfigurationController::RULE_PRIORITIES.to_json.html_safe -%>,
statuses: <%= @options_for_statuses.to_json.html_safe -%>,
- resolutions: <%= @options_for_resolutions.to_json.html_safe -%>
+ resolutions: <%= @options_for_resolutions.to_json.html_safe -%>,
+ favorites: <%= render :partial => 'issues/filter_favourites2' -%>
});
window.SS.NavigatorApp.start();
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb
index 6eb48e182db..6a2893338de 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb
@@ -51,7 +51,11 @@
<%= javascript_include_tag 'select-list' %>
- <%= javascript_include_tag 'navigator/filters' %>
+ <%= javascript_include_tag 'navigator/filters/base-filters' %>
+ <%= javascript_include_tag 'navigator/filters/select-filters' %>
+ <%= javascript_include_tag 'navigator/filters/ajax-select-filters' %>
+ <%= javascript_include_tag 'navigator/filters/range-filters' %>
+ <%= javascript_include_tag 'navigator/filters/favorite-filters' %>
<%= javascript_include_tag 'navigator/app' %>
<%= javascript_include_tag 'application' %>
diff --git a/sonar-server/src/main/webapp/javascripts/navigator/app.js b/sonar-server/src/main/webapp/javascripts/navigator/app.js
index 6a01e062631..5df314f3708 100644
--- a/sonar-server/src/main/webapp/javascripts/navigator/app.js
+++ b/sonar-server/src/main/webapp/javascripts/navigator/app.js
@@ -17,20 +17,17 @@ window.SS = typeof window.SS === 'object' ? window.SS : {};
NavigatorApp.addInitializer(function() {
this.filters = new window.SS.Filters([
+ new window.SS.Filter({
+ type: window.SS.FavoriteFilterView,
+ enabled: true,
+ choices: window.SS.favorites
+ }),
+
new window.SS.Filter({
name: 'Project',
property: 'componentRoots',
- type: window.SS.AjaxSelectFilterView,
- enabled: true,
- select2 : {
- allowClear: true,
- ajax: {
- quietMillis: 300,
- url: baseUrl + '/api/resources/search?f=s2&q=TRK&display_key=true',
- data: function (term, page) { return { s: term, p: page }; },
- results: function (data) { return { more: data.more, results: data.results }; }
- }
- }
+ type: window.SS.ProjectFilterView,
+ enabled: true
}),
new window.SS.Filter({
@@ -38,7 +35,13 @@ window.SS = typeof window.SS === 'object' ? window.SS : {};
property: 'severities[]',
type: window.SS.SelectFilterView,
enabled: true,
- choices: window.SS.severities
+ choices: {
+ 'BLOCKER': 'Blocker',
+ 'CRITICAL': 'Critical',
+ 'MAJOR': 'Major',
+ 'MINOR': 'Minor',
+ 'INFO': 'Info'
+ }
}),
new window.SS.Filter({
@@ -46,52 +49,66 @@ window.SS = typeof window.SS === 'object' ? window.SS : {};
property: 'statuses[]',
type: window.SS.SelectFilterView,
enabled: true,
- choices: window.SS.statuses
+ choices: {
+ 'OPEN': 'Open',
+ 'CONFIRMED': 'Confirmed',
+ 'REOPENED': 'Reopened',
+ 'RESOLVED': 'Resolved',
+ 'CLOSED': 'Closed'
+ }
}),
new window.SS.Filter({
name: 'Resolution',
property: 'resolutions[]',
type: window.SS.SelectFilterView,
- enabled: false,
- choices: window.SS.resolutions
+ enabled: true,
+ choices: {
+ 'FALSE-POSITIVE': 'False positive',
+ 'FIXED': 'Fixed',
+ 'REMOVED': 'Removed'
+ }
}),
new window.SS.Filter({
name: 'Assignee',
property: 'assignees',
- type: window.SS.AjaxSelectFilterView,
- enabled: true,
- select2: {
- allowClear: true,
- query:
- function(query) {
- if (query.term.length === 0) {
- query.callback({results: [{id:'<unassigned>',text:'Unassigned'}]});
- } else if (query.term.length >= 2) {
- $j.ajax(baseUrl + '/api/users/search?f=s2', {
- data: {s: query.term},
- dataType: 'jsonp'
- }).done(function(data) {
- query.callback(data);
- });
- }
- }
- }
+ type: window.SS.AssigneeFilterView,
+ enabled: true//,
+// select2: {
+// allowClear: true,
+// query:
+// function(query) {
+// if (query.term.length === 0) {
+// query.callback({results: [{id:'<unassigned>',text:'Unassigned'}]});
+// } else if (query.term.length >= 2) {
+// $j.ajax(baseUrl + '/api/users/search?f=s2', {
+// data: {s: query.term},
+// dataType: 'jsonp'
+// }).done(function(data) {
+// query.callback(data);
+// });
+// }
+// }
+// }
}),
new window.SS.Filter({
name: 'Created',
propertyFrom: 'createdAfter',
propertyTo: 'createdBefore',
- type: window.SS.RangeFilterView,
- enabled: false
+ type: window.SS.DateRangeFilterView,
+ enabled: true
})
]);
this.filterBarView = new window.SS.FilterBarView({
- collection: this.filters
+ collection: this.filters,
+ extra: {
+ sort: '',
+ asc: false
+ }
});
this.filtersRegion.show(this.filterBarView);
diff --git a/sonar-server/src/main/webapp/javascripts/navigator/filters.js b/sonar-server/src/main/webapp/javascripts/navigator/filters.js
deleted file mode 100644
index 4d99cdf515a..00000000000
--- a/sonar-server/src/main/webapp/javascripts/navigator/filters.js
+++ /dev/null
@@ -1,289 +0,0 @@
-/* global _:false, $j:false, Backbone:false, baseUrl:false */
-
-window.SS = typeof window.SS === 'object' ? window.SS : {};
-
-(function() {
-
- var Filter = Backbone.Model.extend({});
-
-
-
- var Filters = Backbone.Collection.extend({
- model: Filter
- });
-
-
-
- var BaseFilterView = Backbone.Marionette.ItemView.extend({
- template: '#filterTemplate',
- className: 'navigator-filter',
-
-
- events: function() {
- return {};
- },
-
-
- modelEvents: {
- "change:enabled": "render"
- },
-
-
- initialize: function() {
- Backbone.Marionette.ItemView.prototype.initialize.call(this, arguments);
- this.model.view = this;
- },
-
-
- render: function() {
- Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
-
- this.$el.toggleClass(
- 'navigator-filter-disabled',
- !this.model.get('enabled'));
- },
-
-
- renderBody: function() {
- return '';
- },
-
-
- serializeData: function() {
- return _.extend({}, this.model.toJSON(), {
- body: this.renderBody()
- });
- },
-
-
- restore: function() {}
- });
-
-
-
- var SelectFilterView = BaseFilterView.extend({
-
- renderBody: function() {
- var template = _.template($j('#selectFilterTemplate').html());
- return template(this.model.toJSON());
- },
-
-
- onDomRefresh: function() {
- var that = this;
-
- this.$('.navigator-filter-label').hide();
-
- this.$(':input').select2({
- allowClear: false,
- placeholder: this.model.get('name'),
- width: '150px'
- }).on('change', function(e) {
- that.model.set('value', e.val);
- });
- },
-
-
- restore: function() {
- if (this.model.get('value')) {
- this.$(':input').select2('val', this.model.get('value'));
- }
- },
-
-
- focus: function() {}
- });
-
-
-
- var AjaxSelectFilterView = BaseFilterView.extend({
-
- renderBody: function() {
- var template = _.template($j('#ajaxSelectFilterTemplate').html());
- return template(this.model.toJSON());
- },
-
- onDomRefresh: function() {
- var that = this;
-
- this.$('.navigator-filter-label').hide();
-
- this.$(':input').select2(_.extend({
- allowClear: false,
- placeholder: this.model.get('name'),
- width: '150px',
- minimumInputLength: 2
- }, this.model.get('select2')))
- .on('change', function(e) {
- that.model.set('value', e.val);
- });
- },
-
- restore: function() {
- if (this.model.get('value')) {
- this.$(':input').select2('data', this.model.get('value'));
- }
- },
-
- focus: function() {}
- });
-
-
-
- var RangeFilterView = BaseFilterView.extend({
-
- events: function() {
- return _.extend(BaseFilterView.prototype.events.call(), {
- 'change input': 'changeInput'
- });
- },
-
-
- renderBody: function() {
- var template = _.template($j('#rangeFilterTemplate').html());
- return template(this.model.toJSON());
- },
-
-
- onRender: function() {
- this.inputFrom = this.$('[name="' + this.model.get('propertyFrom') + '"]');
- this.inputTo = this.$('[name="' + this.model.get('propertyTo') + '"]');
- },
-
-
- changeInput: function() {
- this.model.set('value', {
- from: this.inputFrom.val(),
- to: this.inputTo.val()
- });
- },
-
-
- restore: function() {
- var value = this.model.get('value');
- if (typeof value === 'object') {
- this.inputFrom.val(value.from || '');
- this.inputTo.val(value.to || '');
- }
- },
-
-
- focus: function() {
- this.inputFrom.focus();
- }
-
- });
-
-
- var FilterBarView = Backbone.Marionette.CompositeView.extend({
- template: '#filterBarTemplate',
- itemViewContainer: '.navigator-filters-list',
-
-
- collectionEvents: {
- 'change:value': 'changeFilters',
- 'change:enabled': 'renderDisabledFilters'
- },
-
-
- ui: {
- disabledFilters: '.navigator-disabled-filters'
- },
-
-
- getItemView: function(item) {
- return item.get('type') || BaseFilterView;
- },
-
-
- itemViewOptions: function() {
- return {
- filterBarView: this
- };
- },
-
-
- render: function() {
- Backbone.Marionette.CompositeView.prototype.render.call(this, arguments);
- this.renderDisabledFilters();
- },
-
-
- renderDisabledFilters: function() {
- var that = this,
- disabledFilters = this.collection.where({ enabled: false });
-
- that.ui.disabledFilters.select2('destroy').empty().show();
-
- if (disabledFilters.length > 0) {
- $j('<option>').appendTo(that.ui.disabledFilters);
- _.each(disabledFilters, function(item) {
- $j('<option>').text(item.get('name')).prop('value', item.cid)
- .appendTo(that.ui.disabledFilters);
- });
- that.ui.disabledFilters.select2({
- allowClear: true,
- placeholder: 'More criteria',
- width: '150px'
- }).on('change', function(e) {
- that.ui.disabledFilters.select2('val', '');
- that.enableFilter(e.val);
- });
- } else {
- that.ui.disabledFilters.hide();
- }
- },
-
-
- enableFilter: function(key) {
- var item = this.collection.find(function(item) {
- return item.cid === key;
- });
-
- if (item) {
- item.view.$el.detach().appendTo(this.itemViewContainer);
- item.set('enabled', true);
- item.view.focus();
- }
- },
-
-
- changeFilters: function() {
- var query = {};
- this.collection.each(function(item) {
- if (item.get('value')) {
- query[item.get('property')] = item.get('value');
- }
- });
- this.applyQuery($j.param(query));
- },
-
-
- applyQuery: function(query) {
- $j.ajax({
- url: baseUrl + '/issues/search',
- type: 'get',
- data: query
- }).done(function(r) {
- $j('.navigator-results').html(r);
- });
- }
- });
-
-
-
- /*
- * Export public classes
- */
-
- _.extend(window.SS, {
- Filter: Filter,
- Filters: Filters,
- BaseFilterView: BaseFilterView,
- FilterBarView: FilterBarView,
- SelectFilterView: SelectFilterView,
- AjaxSelectFilterView: AjaxSelectFilterView,
- RangeFilterView: RangeFilterView
- });
-
-})();
diff --git a/sonar-server/src/main/webapp/javascripts/navigator/filters/ajax-select-filters.js b/sonar-server/src/main/webapp/javascripts/navigator/filters/ajax-select-filters.js
new file mode 100644
index 00000000000..48a6298f775
--- /dev/null
+++ b/sonar-server/src/main/webapp/javascripts/navigator/filters/ajax-select-filters.js
@@ -0,0 +1,260 @@
+/* global _:false, $j:false, Backbone:false, baseUrl:false */
+
+window.SS = typeof window.SS === 'object' ? window.SS : {};
+
+(function() {
+
+ var AssigneeSuggestions = Backbone.Collection.extend({
+
+ parse: function(r) {
+ return r.results;
+ },
+
+
+ url: function() {
+ return baseUrl + '/api/users/search?f=s2';
+ }
+
+ });
+
+
+
+ var ProjectSuggestions = Backbone.Collection.extend({
+
+ parse: function(r) {
+ return r.results;
+ },
+
+
+ url: function() {
+ return baseUrl + '/api/resources/search?f=s2&q=TRK&display_key=true';
+ }
+
+ });
+
+
+
+ var AjaxSelectSuggestionView = Backbone.Marionette.ItemView.extend({
+ template: '#projectSuggestionTemplate',
+ tagName: 'li',
+
+
+ events: {
+ 'change input[type=checkbox]': 'changeSelection'
+ },
+
+
+ changeSelection: function() {
+ this.options.detailsView.updateSelection();
+ },
+
+
+ isModelSelected: function() {
+ var value = this.options.detailsView.model.get('value');
+ return (_.isArray(value) && _.indexOf(value, this.model.get('id')) !== -1);
+ },
+
+
+ serializeData: function() {
+ return _.extend({}, this.model.toJSON(), {
+ selected: this.isModelSelected()
+ });
+ }
+
+ });
+
+
+
+ var AjaxSelectNoSuggestionsView = Backbone.Marionette.ItemView.extend({
+ template: '#projectNoSuggestionsTemplate',
+ tagName: 'li',
+ className: 'single'
+ });
+
+
+
+ var AjaxSelectSuggestionsView = Backbone.Marionette.CollectionView.extend({
+ itemView: AjaxSelectSuggestionView,
+ emptyView: AjaxSelectNoSuggestionsView,
+ tagName: 'ul',
+ className: 'navigator-filter-select-list',
+
+
+ itemViewOptions: function() {
+ return {
+ detailsView: this.options.detailsView
+ };
+ }
+
+ });
+
+
+
+ var AjaxSelectDetailsFilterView = window.SS.DetailsFilterView.extend({
+ template: '#projectFilterTemplate',
+
+
+ initialize: function() {
+ window.SS.DetailsFilterView.prototype.initialize.apply(this, arguments);
+
+ this.suggestions = new ProjectSuggestions();
+ this.suggestionsView = new AjaxSelectSuggestionsView({
+ collection: this.suggestions,
+ model: this.model,
+ detailsView: this
+ });
+ },
+
+
+ bindSearchEvent: function() {
+ var that = this,
+ keyup = function() { that.search(); };
+
+ this.$('.navigator-filter-search input')
+ .on('keyup', $j.debounce(250, keyup));
+ },
+
+
+ onRender: function() {
+ this.bindSearchEvent();
+ this.suggestionsView.$el.appendTo(this.$el);
+ this.suggestions.reset([]);
+ },
+
+
+ onShow: function() {
+ this.$('.navigator-filter-search input').focus();
+ },
+
+
+ search: function() {
+ var query = this.$('.navigator-filter-search input').val();
+ if (query.length > 1) {
+ this.suggestions.fetch({
+ data: { s: query },
+ reset: true
+ });
+ } else {
+ this.suggestions.reset([]);
+ }
+ },
+
+
+ updateSelection: function() {
+ var value = this.$('input[type=checkbox]:checked').map(function () {
+ return $j(this).val();
+ }).get(),
+
+ textValue = this.$('input[type=checkbox]:checked').map(function () {
+ return $j(this).next().text();
+ }).get();
+
+ this.model.set({
+ value: value,
+ textValue: textValue
+ });
+ },
+
+
+ serializeData: function() {
+ return _.extend({}, this.model.toJSON(), {
+ suggestions: this.suggestions.toJSON()
+ });
+ }
+
+ });
+
+
+
+ var AjaxSelectFilterView = window.SS.BaseFilterView.extend({
+
+ initialize: function() {
+ window.SS.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: AjaxSelectDetailsFilterView
+ });
+ },
+
+
+ renderValue: function() {
+ var value = this.model.get('textValue');
+ return this.isDefaultValue() ? 'All' : value.join(', ');
+ },
+
+
+ isDefaultValue: function() {
+ var value = this.model.get('value');
+ return !(_.isArray(value) && value.length > 0);
+ }
+
+ });
+
+
+
+ var DetailsProjectFilterView = AjaxSelectDetailsFilterView.extend({
+
+ initialize: function() {
+ AjaxSelectDetailsFilterView.prototype.initialize.apply(this, arguments);
+
+ this.suggestions = new ProjectSuggestions();
+ this.suggestionsView = new AjaxSelectSuggestionsView({
+ collection: this.suggestions,
+ model: this.model,
+ detailsView: this
+ });
+ }
+
+ });
+
+
+
+ var ProjectFilterView = AjaxSelectFilterView.extend({
+
+ initialize: function() {
+ window.SS.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsProjectFilterView
+ });
+ }
+
+ });
+
+
+
+ var DetailsAssigneeFilterView = AjaxSelectDetailsFilterView.extend({
+
+ initialize: function() {
+ AjaxSelectDetailsFilterView.prototype.initialize.apply(this, arguments);
+
+ this.suggestions = new AssigneeSuggestions();
+ this.suggestionsView = new AjaxSelectSuggestionsView({
+ collection: this.suggestions,
+ model: this.model,
+ detailsView: this
+ });
+ }
+
+ });
+
+
+
+ var AssigneeFilterView = AjaxSelectFilterView.extend({
+
+ initialize: function() {
+ window.SS.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsAssigneeFilterView
+ });
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ _.extend(window.SS, {
+ ProjectFilterView: ProjectFilterView,
+ AssigneeFilterView: AssigneeFilterView
+ });
+
+})();
diff --git a/sonar-server/src/main/webapp/javascripts/navigator/filters/base-filters.js b/sonar-server/src/main/webapp/javascripts/navigator/filters/base-filters.js
new file mode 100644
index 00000000000..15b302ea016
--- /dev/null
+++ b/sonar-server/src/main/webapp/javascripts/navigator/filters/base-filters.js
@@ -0,0 +1,228 @@
+/* global _:false, $j:false, Backbone:false, baseUrl:false */
+
+window.SS = typeof window.SS === 'object' ? window.SS : {};
+
+(function() {
+
+ var Filter = Backbone.Model.extend({});
+
+
+
+ var Filters = Backbone.Collection.extend({
+ model: Filter
+ });
+
+
+
+ var DetailsFilterView = Backbone.Marionette.ItemView.extend({
+ template: '#detailsFilterTemplate',
+ className: 'navigator-filter-details',
+
+ onShow: function() {},
+ onHide: function() {}
+ });
+
+
+
+ var BaseFilterView = Backbone.Marionette.ItemView.extend({
+ template: '#baseFilterTemplate',
+ className: 'navigator-filter',
+
+
+ events: function() {
+ return {
+ 'click': 'toggleDetails'
+ };
+ },
+
+
+ initialize: function(options) {
+ Backbone.Marionette.ItemView.prototype.initialize.apply(this, arguments);
+
+ var detailsView = options.detailsView || DetailsFilterView;
+ this.detailsView = new detailsView({
+ model: this.model
+ });
+ this.detailsView.render = this.renderDetails;
+
+ this.model.view = this;
+
+ this.listenTo(this.model, 'change:value', this.renderBase);
+ },
+
+
+ attachDetailsView: function() {
+ this.detailsView.$el.detach().appendTo($j('body'));
+ },
+
+
+ render: function() {
+ this.renderBase();
+
+ this.attachDetailsView();
+ this.detailsView.render.call(this.detailsView);
+
+ this.$el.toggleClass(
+ 'navigator-filter-disabled',
+ !this.model.get('enabled'));
+ },
+
+
+ renderBase: function() {
+ Backbone.Marionette.ItemView.prototype.render.apply(this, arguments);
+ },
+
+
+ toggleDetails: function(e) {
+ e.stopPropagation();
+ if (this.$el.hasClass('active')) {
+ this.hideDetails();
+ } else {
+ this.showDetails();
+ }
+ },
+
+
+ showDetails: function() {
+ this.registerShowedDetails();
+
+ var top = this.$el.offset().top + this.$el.outerHeight(),
+ left = this.$el.offset().left;
+
+ this.detailsView.$el.css({ top: top, left: left }).addClass('active');
+ this.$el.addClass('active');
+ this.detailsView.onShow();
+ },
+
+
+ registerShowedDetails: function() {
+ this.options.filterBarView.hideDetails();
+ this.options.filterBarView.showedView = this;
+ },
+
+
+ hideDetails: function() {
+ this.detailsView.$el.removeClass('active');
+ this.$el.removeClass('active');
+ this.detailsView.onHide();
+ },
+
+
+ renderValue: function() {
+ return this.model.get('value') || 'unset';
+ },
+
+
+ renderDetails: function() {
+ Backbone.Marionette.ItemView.prototype.render.apply(this, arguments);
+ },
+
+
+ isDefaultValue: function() {
+ return true;
+ },
+
+
+ serializeData: function() {
+ return _.extend({}, this.model.toJSON(), {
+ value: this.renderValue(),
+ defaultValue: this.isDefaultValue()
+ });
+ }
+
+ });
+
+
+
+ var FilterBarView = Backbone.Marionette.CompositeView.extend({
+ template: '#filterBarTemplate',
+ itemViewContainer: '.navigator-filters-list',
+
+
+ collectionEvents: {
+ 'change:value': 'changeFilters'
+ },
+
+
+ ui: {
+ disabledFilters: '.navigator-disabled-filters'
+ },
+
+
+ getItemView: function(item) {
+ return item.get('type') || BaseFilterView;
+ },
+
+
+ itemViewOptions: function() {
+ return {
+ filterBarView: this
+ };
+ },
+
+
+ initialize: function() {
+ Backbone.Marionette.CompositeView.prototype.initialize.apply(this, arguments);
+ var that = this;
+ $('body').on('click', function() {
+ that.hideDetails();
+ });
+ },
+
+
+ render: function() {
+ Backbone.Marionette.CompositeView.prototype.render.apply(this, arguments);
+ },
+
+
+ hideDetails: function() {
+ if (_.isObject(this.showedView)) {
+ this.showedView.hideDetails();
+ }
+ },
+
+
+ changeFilters: function() {
+ var query = _.clone(this.options.extra);
+ this.collection.each(function(item) {
+ var value = item.get('value');
+
+ if (value) {
+ if (_.isObject(value) && !_.isArray(value)) {
+ _.extend(query, value);
+ } else {
+ query[item.get('property')] = item.get('value');
+ }
+ }
+
+ });
+ this.applyQuery($j.param(query));
+ },
+
+
+ applyQuery: function(query) {
+ $j.ajax({
+ url: baseUrl + '/issues/search',
+ type: 'get',
+ data: query
+ }).done(function(r) {
+ $j('.navigator-results').html(r);
+ });
+ }
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ _.extend(window.SS, {
+ Filter: Filter,
+ Filters: Filters,
+ BaseFilterView: BaseFilterView,
+ DetailsFilterView: DetailsFilterView,
+ FilterBarView: FilterBarView
+ });
+
+})();
diff --git a/sonar-server/src/main/webapp/javascripts/navigator/filters/favorite-filters.js b/sonar-server/src/main/webapp/javascripts/navigator/filters/favorite-filters.js
new file mode 100644
index 00000000000..52971fe3002
--- /dev/null
+++ b/sonar-server/src/main/webapp/javascripts/navigator/filters/favorite-filters.js
@@ -0,0 +1,63 @@
+/* global _:false, $j:false, baseUrl:false */
+
+window.SS = typeof window.SS === 'object' ? window.SS : {};
+
+(function() {
+
+ var DetailsFavoriteFilterView = window.SS.DetailsFilterView.extend({
+ template: '#detailsFavoriteFilterTemplate',
+
+
+ events: {
+ 'click label[data-id]': 'applyFavorite',
+ 'click .manage label': 'manage'
+ },
+
+
+ applyFavorite: function(e) {
+ var id = $j(e.target).data('id');
+ window.location = baseUrl + '/issues/filter/' + id;
+ },
+
+
+ manage: function() {
+ window.location = baseUrl + '/issues/manage';
+ }
+
+ });
+
+
+
+ var FavoriteFilterView = window.SS.SelectFilterView.extend({
+ template: '#favoriteFilterTemplate',
+
+
+ initialize: function() {
+ window.SS.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsFavoriteFilterView
+ });
+ },
+
+
+ renderValue: function() {
+ return '';
+ },
+
+
+ isDefaultValue: function() {
+ return false;
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ _.extend(window.SS, {
+ FavoriteFilterView: FavoriteFilterView
+ });
+
+})();
diff --git a/sonar-server/src/main/webapp/javascripts/navigator/filters/range-filters.js b/sonar-server/src/main/webapp/javascripts/navigator/filters/range-filters.js
new file mode 100644
index 00000000000..088cdd4165a
--- /dev/null
+++ b/sonar-server/src/main/webapp/javascripts/navigator/filters/range-filters.js
@@ -0,0 +1,99 @@
+/* global _:false, $j:false, Backbone:false, baseUrl:false */
+
+window.SS = typeof window.SS === 'object' ? window.SS : {};
+
+(function() {
+
+ var DetailsRangeFilterView = window.SS.DetailsFilterView.extend({
+ template: '#rangeFilterTemplate',
+
+
+ events: {
+ 'change input': 'change'
+ },
+
+
+ change: function() {
+ var value = {},
+ valueFrom = this.$('input').eq(0).val(),
+ valueTo = this.$('input').eq(1).val();
+
+ if (valueFrom.length > 0) {
+ value[this.model.get('propertyFrom')] = valueFrom;
+ }
+
+ if (valueTo.length > 0) {
+ value[this.model.get('propertyTo')] = valueTo;
+ }
+
+ this.model.set('value', value);
+ }
+
+ });
+
+
+
+ var RangeFilterView = window.SS.BaseFilterView.extend({
+
+ initialize: function() {
+ window.SS.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsRangeFilterView
+ });
+ },
+
+
+ renderValue: function() {
+ var value = this.model.get('value');
+ if (_.isObject(value) && (value.from || value.to)) {
+ return 'From ' + (value.from || '') + ' to ' + (value.to || '');
+ } else {
+ return 'Any';
+ }
+ }
+
+ });
+
+
+
+ var DateRangeFilterView = RangeFilterView.extend({
+
+ render: function() {
+ RangeFilterView.prototype.render.apply(this, arguments);
+ this.detailsView.$('input').prop('placeholder', '1970-01-01');
+ },
+
+
+ renderValue: function() {
+ if (!this.isDefaultValue()) {
+ var value = _.values(this.model.get('value'));
+ return value.join(' — ');
+ } else {
+ return 'Anytime';
+ }
+ },
+
+
+ isDefaultValue: function() {
+ var value = this.model.get('value'),
+ propertyFrom = this.model.get('propertyFrom'),
+ propertyTo = this.model.get('propertyTo'),
+ valueFrom = _.isObject(value) && value[propertyFrom],
+ valueTo = _.isObject(value) && value[propertyTo];
+
+ return !valueFrom && !valueTo;
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ _.extend(window.SS, {
+ RangeFilterView: RangeFilterView,
+ DateRangeFilterView: DateRangeFilterView
+ });
+
+})();
diff --git a/sonar-server/src/main/webapp/javascripts/navigator/filters/select-filters.js b/sonar-server/src/main/webapp/javascripts/navigator/filters/select-filters.js
new file mode 100644
index 00000000000..d10391a7351
--- /dev/null
+++ b/sonar-server/src/main/webapp/javascripts/navigator/filters/select-filters.js
@@ -0,0 +1,66 @@
+/* global _:false, $j:false, Backbone:false, baseUrl:false */
+
+window.SS = typeof window.SS === 'object' ? window.SS : {};
+
+(function() {
+
+ var DetailsSelectFilterView = window.SS.DetailsFilterView.extend({
+ template: '#selectFilterTemplate',
+
+
+ events: {
+ 'change input[type=checkbox]': 'changeSelection'
+ },
+
+
+ changeSelection: function() {
+ var value = this.$('input[type=checkbox]:checked').map(function() {
+ return $j(this).val();
+ }).get();
+ this.model.set('value', value);
+ }
+ });
+
+
+
+ var SelectFilterView = window.SS.BaseFilterView.extend({
+
+ initialize: function() {
+ window.SS.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsSelectFilterView
+ });
+ },
+
+
+ renderValue: function() {
+ var choices = this.model.get('choices'),
+ value = (this.model.get('value') || []).map(function(key) {
+ return choices[key];
+ });
+
+ return this.isDefaultValue() ? 'All' : value.join(', ');
+ },
+
+
+ isDefaultValue: function() {
+ var value = this.model.get('value'),
+ choices = this.model.get('choices');
+
+ return !(_.isArray(value) &&
+ value.length > 0 &&
+ value.length < Object.keys(choices).length);
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ _.extend(window.SS, {
+ SelectFilterView: SelectFilterView
+ });
+
+})();
diff --git a/sonar-server/src/main/webapp/stylesheets/navigator.css b/sonar-server/src/main/webapp/stylesheets/navigator.css
index 146ed8be9bf..915976b925b 100644
--- a/sonar-server/src/main/webapp/stylesheets/navigator.css
+++ b/sonar-server/src/main/webapp/stylesheets/navigator.css
@@ -1,45 +1,15 @@
/*
+ * Fonts
+ */
+/*
* Colors
*/
-.navigator .select2-container .select2-choice,
-.navigator .select2-container-multi .select2-choices {
- border: 1px solid #aaa;
- border-radius: 0;
- background: #ffffff;
-}
-.navigator .select2-container-active .select2-choice,
-.navigator .select2-container-active .select2-choices {
- box-shadow: none;
-}
-.navigator .select2-container-multi .select2-choices {
- white-space: nowrap;
-}
-.navigator .select2-container-multi .select2-choices li {
- float: none;
- display: inline-block;
- vertical-align: middle;
-}
.navigator-filters {
margin-bottom: 10px;
- padding: 10px;
- border: 1px solid #cdcdcd;
+ border-bottom: 1px solid #cdcdcd;
background: #efefef;
font-size: 0;
}
-.navigator-filters-favorite {
- display: inline-block;
- vertical-align: middle;
- margin-right: 15px;
- padding-right: 14px;
- border-right: 1px solid #cdcdcd;
-}
-.navigator-filters-favorite-toggle {
- display: block;
- width: 16px;
- height: 16px;
- background: url(../images/star.png) no-repeat left center;
- cursor: pointer;
-}
.navigator-filters-list {
display: inline-block;
vertical-align: middle;
@@ -49,14 +19,27 @@
display: inline-block;
vertical-align: middle;
margin-left: 20px;
- font-size: 14px;
+ font-size: 13px;
}
.navigator-filter {
display: inline-block;
vertical-align: top;
+ height: 36px;
+ line-height: 36px;
+ padding: 0 7px;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: background 0.3s ease;
}
-.navigator-filter + .navigator-filter {
- margin-left: 20px;
+.navigator-filter:hover {
+ background-color: #e2e2e2;
+}
+.navigator-filter.active {
+ position: relative;
+ padding: 0 6px;
+ border-left: 1px solid #cdcdcd;
+ border-right: 1px solid #cdcdcd;
+ background: #fff;
}
.navigator-filter-disabled {
display: none;
@@ -65,18 +48,85 @@
display: inline-block;
vertical-align: middle;
margin-right: 5px;
- color: #666;
- font-size: 14px;
+ color: #333;
+ font-size: 13px;
}
.navigator-filter-label:after {
content: ":";
}
-.navigator-filter-body {
+.navigator-filter-value {
display: inline-block;
vertical-align: middle;
- color: #000;
- font-size: 14px;
+ max-width: 120px;
+ color: #333;
+ font-size: 13px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.navigator-filter-value.default {
+ color: #666;
}
.navigator-filter-range-input {
- width: 80px;
+ width: 120px;
+}
+.navigator-filter-details {
+ display: none;
+ position: absolute;
+ z-index: 1200;
+ min-width: 100px;
+ border: 1px solid #cdcdcd;
+ background: #fff;
+ font-size: 13px;
+ transition: opacity 0.3s ease;
+}
+.navigator-filter-details.active {
+ display: block;
+}
+.navigator-filter-details-inner {
+ padding: 5px 7px;
+}
+.navigator-filter-select-list {
+ min-width: 150px;
+ max-height: 182px;
+ padding: 5px 0;
+ overflow-y: auto;
+}
+.navigator-filter-select-list label {
+ display: block;
+ padding: 5px 7px;
+ transition: background 0.3s ease;
+ cursor: pointer;
+}
+.navigator-filter-select-list label:hover {
+ background-color: #ededed;
+}
+.navigator-filter-select-list input[type=checkbox] {
+ position: relative;
+ top: -1px;
+ cursor: pointer;
+}
+.navigator-filter-select-list .single {
+ padding: 5px 7px;
+}
+.navigator-filter-select-list .line {
+ height: 1px;
+ margin: 5px 0;
+ background: #cdcdcd;
+}
+.navigator-filter-search {
+ margin: 7px;
+}
+.navigator-filter-search input {
+ width: 100%;
+ height: 26px;
+ padding: 0 7px;
+ border: 1px solid #cdcdcd;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.navigator-filter-favorite {
+ width: 16px;
+ height: 36px;
+ background: url('../images/star.png') no-repeat center center;
}
diff --git a/sonar-server/src/main/webapp/stylesheets/navigator.less b/sonar-server/src/main/webapp/stylesheets/navigator.less
index 031988f5f61..789d0f61e1c 100644
--- a/sonar-server/src/main/webapp/stylesheets/navigator.less
+++ b/sonar-server/src/main/webapp/stylesheets/navigator.less
@@ -1,55 +1,21 @@
@import "mixins";
@import "variables";
-.navigator {
-
- .select2-container .select2-choice,
- .select2-container-multi .select2-choices {
- border: 1px solid #aaa;
- border-radius: 0;
- background: @white;
- }
+@navigatorHeight: 36px;
+@navigatorHover: darken(@grey, 5%);
+@navigatorFilterPadding: 7px;
- .select2-container-active .select2-choice,
- .select2-container-active .select2-choices {
- box-shadow: none;
- }
-
- .select2-container-multi .select2-choices {
- white-space: nowrap;
- }
-
- .select2-container-multi .select2-choices li {
- float: none;
- display: inline-block;
- vertical-align: middle;
- }
+.navigator {
}
.navigator-filters {
margin-bottom: 10px;
- padding: 10px;
- border: 1px solid @darkGrey;
+ border-bottom: 1px solid @darkGrey;
background: @grey;
font-size: 0;
}
-.navigator-filters-favorite {
- display: inline-block;
- vertical-align: middle;
- margin-right: 15px;
- padding-right: 14px;
- border-right: 1px solid #cdcdcd;
-}
-
-.navigator-filters-favorite-toggle {
- display: block;
- .size(16px, 16px);
- background: url(../images/star.png) no-repeat left center;
- cursor: pointer;
-}
-
.navigator-filters-list {
display: inline-block;
vertical-align: middle;
@@ -60,16 +26,30 @@
display: inline-block;
vertical-align: middle;
margin-left: 20px;
- font-size: 14px;
+ font-size: @baseFontSize;
}
.navigator-filter {
display: inline-block;
vertical-align: top;
-}
+ height: @navigatorHeight;
+ line-height: @navigatorHeight;
+ padding: 0 @navigatorFilterPadding;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: background 0.3s ease;
-.navigator-filter + .navigator-filter {
- margin-left: 20px;
+ &:hover {
+ background-color: @navigatorHover;
+ }
+
+ &.active {
+ position: relative;
+ padding: 0 @navigatorFilterPadding - 1px;
+ border-left: 1px solid #cdcdcd;
+ border-right: 1px solid #cdcdcd;
+ background: #fff;
+ }
}
.navigator-filter-disabled {
@@ -80,21 +60,98 @@
display: inline-block;
vertical-align: middle;
margin-right: 5px;
- color: #666;
- font-size: 14px;
+ color: #333;
+ font-size: @baseFontSize;
&:after { content: ":"; }
}
-.navigator-filter-body {
+.navigator-filter-value {
display: inline-block;
vertical-align: middle;
- color: #000;
- font-size: 14px;
+ max-width: 120px;
+ color: #333;
+ font-size: @baseFontSize;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &.default {
+ color: #666;
+ }
}
.navigator-filter-range-input {
- width: 80px;
+ width: 120px;
+}
+
+.navigator-filter-details {
+ display: none;
+ position: absolute;
+ z-index: 1200;
+ min-width: 100px;
+ border: 1px solid #cdcdcd;
+ background: #fff;
+ font-size: @baseFontSize;
+ transition: opacity 0.3s ease;
+
+ &.active {
+ display: block;
+ }
+}
+
+.navigator-filter-details-inner {
+ padding: 5px @navigatorFilterPadding;
+}
+
+.navigator-filter-select-list {
+ min-width: 150px;
+ max-height: 182px;
+ padding: 5px 0;
+ overflow-y: auto;
+
+ label {
+ display: block;
+ padding: 5px @navigatorFilterPadding;
+ transition: background 0.3s ease;
+ cursor: pointer;
+
+ &:hover {
+ background-color: darken(#fff, 7%);
+ }
+ }
+
+ input[type=checkbox] {
+ position: relative;
+ top: -1px;
+ cursor: pointer;
+ }
+
+ .single {
+ padding: 5px @navigatorFilterPadding;
+ }
+
+ .line {
+ height: 1px;
+ margin: 5px 0;
+ background: #cdcdcd;
+ }
+}
+
+.navigator-filter-search {
+ margin: @navigatorFilterPadding;
+
+ input {
+ .size(100%, 26px);
+ padding: 0 7px;
+ border: 1px solid #cdcdcd;
+ .box-sizing(border-box);
+ }
+}
+
+.navigator-filter-favorite {
+ .size(16px, @navigatorHeight);
+ background: url('../images/star.png') no-repeat center center;
}
.navigator-results {
diff --git a/sonar-server/src/main/webapp/stylesheets/variables.css b/sonar-server/src/main/webapp/stylesheets/variables.css
index cf549c1e46d..6fbfac8e716 100644
--- a/sonar-server/src/main/webapp/stylesheets/variables.css
+++ b/sonar-server/src/main/webapp/stylesheets/variables.css
@@ -1,3 +1,6 @@
/*
+ * Fonts
+ */
+/*
* Colors
*/
diff --git a/sonar-server/src/main/webapp/stylesheets/variables.less b/sonar-server/src/main/webapp/stylesheets/variables.less
index 80028758a9c..354f666ed0a 100644
--- a/sonar-server/src/main/webapp/stylesheets/variables.less
+++ b/sonar-server/src/main/webapp/stylesheets/variables.less
@@ -1,4 +1,12 @@
/*
+ * Fonts
+ */
+
+@baseFontSize: 13px;
+
+
+
+/*
* Colors
*/
diff --git a/sonar-server/wro.xml b/sonar-server/wro.xml
index a5272ad6db0..58be3d5dbc8 100644
--- a/sonar-server/wro.xml
+++ b/sonar-server/wro.xml
@@ -31,7 +31,11 @@
<js>/javascripts/select-list.js</js>
- <js>/javascripts/navigator/filters.js</js>
+ <js>/javascripts/navigator/filters/base-filters.js</js>
+ <js>/javascripts/navigator/filters/select-filters.js</js>
+ <js>/javascripts/navigator/filters/ajax-select-filters.js</js>
+ <js>/javascripts/navigator/filters/range-filters.js</js>
+ <js>/javascripts/navigator/filters/favorite-filters.js</js>
<js>/javascripts/navigator/app.js</js>
<js>/javascripts/application.js</js>