]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6285 show filters on the issues home page
authorStas Vilchik <vilchiks@gmail.com>
Fri, 11 Sep 2015 12:40:32 +0000 (14:40 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Fri, 11 Sep 2015 12:40:32 +0000 (14:40 +0200)
server/sonar-web/src/main/js/apps/issues/controller.js
server/sonar-web/src/main/js/apps/issues/filters-view.js
server/sonar-web/src/main/js/apps/issues/templates/issues-filters.hbs
server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-home.hbs
server/sonar-web/src/main/js/apps/issues/workspace-home-view.js
server/sonar-web/src/main/js/components/common/handlebars-extensions.js
server/sonar-web/src/main/less/pages/issues.less
server/sonar-web/src/test/json/issues-spec/issue-filters.json
server/sonar-web/test/medium/issues.spec.js

index a35311fa576194a7f018776c88e73cbacaef2961..a201496f8b8a0e4736384d6827ebcb81affc0dcc 100644 (file)
@@ -90,7 +90,7 @@ define([
     fetchFilters: function () {
       var that = this;
       return $.when(
-          that.options.app.filters.fetch(),
+          that.options.app.filters.fetch({ reset: true }),
           $.get(baseUrl + '/api/issue_filters/app', function (r) {
             that.options.app.state.set({
               canBulkChange: r.canBulkChange,
@@ -237,7 +237,10 @@ define([
       this.options.app.layout.workspaceComponentViewerRegion.reset();
       key.setScope('home');
       this.options.app.issuesView.unbindScrollEvents();
-      this.options.app.homeView = new HomeView({ app: this.options.app });
+      this.options.app.homeView = new HomeView({
+        app: this.options.app,
+        collection: this.options.app.filters
+      });
       this.options.app.layout.workspaceHomeRegion.show(this.options.app.homeView);
       return this.options.app.layout.showHomePage();
     },
index d219c1e0fd44bb766add84028f6eb2522b126c95..c3ea67bd4340531f87fb01c95b8d0df01a563897 100644 (file)
@@ -20,7 +20,7 @@ define([
       var that = this;
       this.listenTo(options.app.state, 'change:filter', this.render);
       this.listenTo(options.app.state, 'change:changed', this.render);
-      this.listenTo(options.app.filters, 'all', this.render);
+      this.listenTo(options.app.filters, 'reset', this.render);
       window.onSaveAs = window.onCopy = window.onEdit = function (id) {
         $('#modal').dialog('close');
         return that.options.app.controller.fetchFilters().done(function () {
index c3ffd012a3a67b684ad979e16e77e1f289e537c0..74cd306ce3e68fbee81d4cd9be770764ac687349 100644 (file)
@@ -1,29 +1,27 @@
 {{#unless state.isContext}}
   <h1 class="page-title dropdown">
-    {{#if state.canManageFilters}}
-      <a class="search-navigator-filters-show-list dropdown-toggle" data-toggle="dropdown">
-        <i class="icon-list"></i><span class="issues-filters-name">{{> "_issues-filter-name"}}</span>
-      </a>
-      <ul class="dropdown-menu">
-        {{#each items}}
+    <a class="search-navigator-filters-show-list dropdown-toggle" data-toggle="dropdown">
+      <i class="icon-list"></i><span class="issues-filters-name">{{> "_issues-filter-name"}}</span>
+    </a>
+    <ul class="dropdown-menu">
+      {{#each items}}
+        {{#ifCanUseFilter query}}
           <li>
             <a class="search-navigator-filters-button search-navigator-filters-filter js-filter" data-id="{{id}}">
               {{name}}
             </a>
           </li>
-        {{/each}}
-        {{#notEmpty items}}
-          <li class="divider"></li>
-        {{/notEmpty}}
+        {{/ifCanUseFilter}}
+      {{/each}}
+      {{#if state.canManageFilters}}
+        <li class="divider"></li>
         <li>
           <a class="search-navigator-filters-manage" href="{{link "/issues/manage"}}">{{t "manage"}}</a>
         </li>
-      </ul>
-      {{#if filter.description}}
-        <div class="search-navigator-filters-description">{{filter.description}}</div>
       {{/if}}
-    {{else}}
-      <span class="search-navigator-filters-name">{{t "issues"}}</span>
+    </ul>
+    {{#if filter.description}}
+      <div class="search-navigator-filters-description">{{filter.description}}</div>
     {{/if}}
   </h1>
 
index 6c903b7faf9ff1e8a61b7c74e4242e7bbbb26084..0057630ed6ae36ffef38b06b871fd879f0ae2c64 100644 (file)
@@ -1,77 +1,26 @@
-<div class="spacer-top spacer-bottom">
-  <div class="columns">
-    <div class="column-half {{#unless user}}column-one{{/unless}}">
-      <h3 class="text-center">{{t "issues.home.recent_issues"}}</h3>
-      <p class="note text-center">({{t "issues.home.over_last_week"}})</p>
+<div class="issues-workspace-home-inner">
+  <div>
+    <h2 class="big-spacer-bottom">Start From Filters:</h2>
 
-      <div class="spacer-top text-center js-barchart" data-height="75" data-width="300"></div>
-      <h4 class="spacer-top spacer-bottom text-center">{{t "issues.home.projects"}}</h4>
-      <table class="data zebra spacer-top">
-        {{#each projects}}
-          <tr>
-            <td>{{qualifierIcon "TRK"}}&nbsp;<a href="{{issuesHomeLink "projectUuids" val}}">{{label}}</a></td>
-            <td class="thin text-right">+{{numberShort count}}</td>
-          </tr>
-        {{/each}}
-      </table>
-      <h4 class="spacer-top spacer-bottom text-center">{{t "issues.home.authors"}}</h4>
-      <table class="data zebra spacer-top">
-        {{#each authors}}
-          <tr>
-            <td><a href="{{issuesHomeLink "authors" val}}">{{val}}</a></td>
-            <td class="thin text-right">+{{numberShort count}}</td>
-          </tr>
-        {{/each}}
-      </table>
-      <h4 class="spacer-top spacer-bottom text-center">{{t "issues.home.tags"}}</h4>
-      <ul class="list-inline">
-        {{#each tags}}
-          <li><a class="link-no-underline" style="font-size: {{size}}px;" data-toggle="tooltip" data-placement="bottom"
-                 href="{{issuesHomeLink "tags" val}}"
-                 title="+{{numberShort count}}">{{val}}</a></li>
-        {{/each}}
-      </ul>
-    </div>
+    <ul>
+      {{#each items}}
+        {{#ifCanUseFilter query}}
+          <li class="text-left spacer-bottom">
+            <i class="icon-favorite little-spacer-right" {{#unless favorite}}style="visibility: hidden;"{{/unless}}></i>
 
-    {{#if user}}
-      <div class="column-half">
-        <h3 class="text-center">{{t "issues.home.my_recent_issues"}}</h3>
-        <p class="note text-center">({{t "issues.home.over_last_week"}})</p>
+            <a href="#id={{id}}">{{name}}</a>
 
-        <div class="spacer-top text-center js-my-barchart" data-height="75" data-width="300"></div>
-        {{#notEmpty myProjects}}
-          <h4 class="spacer-top spacer-bottom text-center">{{t "issues.home.projects"}}</h4>
-          <table class="data zebra spacer-top">
-            {{#each myProjects}}
-              <tr>
-                <td>{{qualifierIcon "TRK"}}&nbsp;<a href="{{myIssuesHomeLink "projectUuids" val}}">{{label}}</a></td>
-                <td class="thin text-right">+{{numberShort count}}</td>
-              </tr>
-            {{/each}}
-          </table>
-        {{/notEmpty}}
-        {{#notEmpty myTags}}
-          <h4 class="spacer-top spacer-bottom text-center">{{t "issues.home.tags"}}</h4>
-          <ul class="list-inline">
-            {{#each myTags}}
-              <li><a class="link-no-underline" style="font-size: {{size}}px;" data-toggle="tooltip" data-placement="bottom"
-                     href="{{myIssuesHomeLink "tags" val}}"
-                     title="+{{numberShort count}}">{{val}}</a></li>
-            {{/each}}
-          </ul>
-        {{/notEmpty}}
-        {{#notEmpty filters}}
-          <h3 class="spacer-bottom text-center" style="padding-top: 12px; margin-top: 20px; border-top: 1px solid #efefef;">
-            {{t "issues.home.my_filters"}}</h3>
-          <ul class="list-inline">
-            {{#each filters}}
-              <li>
-                <a href="{{issueFilterHomeLink id}}">{{name}}</a>
-              </li>
-            {{/each}}
-          </ul>
-        {{/notEmpty}}
-      </div>
-    {{/if}}
+            {{#if shared}}
+              <span class="note nowrap">[{{t "issue_filter.shared"}}]</span>
+            {{/if}}
+          </li>
+        {{/ifCanUseFilter}}
+      {{/each}}
+    </ul>
+  </div>
+
+  <div class="big-spacer-top">
+    or make a
+    <a href="#resolved=false" class="button spacer-left">New Search</a>
   </div>
 </div>
index 942e86e4ba7779ebe64c14580250366daf1960e6..aff1c52366fac29027241709d8b3e7b3c9eb7ea3 100644 (file)
@@ -2,169 +2,12 @@ define([
   './templates'
 ], function () {
 
-  var $ = jQuery;
-
-  Handlebars.registerHelper('issuesHomeLink', function (property, value) {
-    return baseUrl + '/issues/search#resolved=false|createdInLast=1w|' +
-        property + '=' + (encodeURIComponent(value));
-  });
-
-  Handlebars.registerHelper('myIssuesHomeLink', function (property, value) {
-    return baseUrl + '/issues/search#resolved=false|createdInLast=1w|assignees=__me__|' +
-        property + '=' + (encodeURIComponent(value));
-  });
-
   Handlebars.registerHelper('issueFilterHomeLink', function (id) {
     return baseUrl + '/issues/search#id=' + id;
   });
 
   return Marionette.ItemView.extend({
-    template: Templates['issues-workspace-home'],
-
-    modelEvents: {
-      'change': 'render'
-    },
-
-    events: {
-      'click .js-barchart rect': 'selectBar',
-      'click .js-my-barchart rect': 'selectMyBar'
-    },
-
-    initialize: function () {
-      this.model = new Backbone.Model();
-      this.requestIssues();
-      this.requestMyIssues();
-    },
-
-    _getProjects: function (r) {
-      var projectFacet = _.findWhere(r.facets, { property: 'projectUuids' });
-      if (projectFacet != null) {
-        var values = _.head(projectFacet.values, 3);
-        values.forEach(function (v) {
-          var project = _.findWhere(r.components, { uuid: v.val });
-          v.label = project.longName;
-        });
-        return values;
-      }
-    },
-
-    _getAuthors: function (r) {
-      var authorFacet = _.findWhere(r.facets, { property: 'authors' });
-      if (authorFacet != null) {
-        return _.head(authorFacet.values, 3);
-      }
-    },
-
-    _getTags: function (r) {
-      var MIN_SIZE = 10,
-          MAX_SIZE = 24,
-          tagFacet = _.findWhere(r.facets, { property: 'tags' });
-      if (tagFacet != null) {
-        var values = _.head(tagFacet.values, 10),
-            minCount = _.min(values, function (v) {
-              return v.count;
-            }).count,
-            maxCount = _.max(values, function (v) {
-              return v.count;
-            }).count,
-            scale = d3.scale.linear().domain([minCount, maxCount]).range([MIN_SIZE, MAX_SIZE]);
-        values.forEach(function (v) {
-          v.size = scale(v.count);
-        });
-        return values;
-      }
-    },
-
-    requestIssues: function () {
-      var that = this;
-      var url = baseUrl + '/api/issues/search',
-          options = {
-            resolved: false,
-            createdInLast: '1w',
-            ps: 1,
-            facets: 'createdAt,projectUuids,authors,tags'
-          };
-      return $.get(url, options).done(function (r) {
-        var createdAt = _.findWhere(r.facets, { property: 'createdAt' });
-        that.model.set({
-          createdAt: createdAt != null ? createdAt.values : null,
-          projects: that._getProjects(r),
-          authors: that._getAuthors(r),
-          tags: that._getTags(r)
-        });
-      });
-    },
-
-    requestMyIssues: function () {
-      var that = this;
-      var url = baseUrl + '/api/issues/search',
-          options = {
-            resolved: false,
-            createdInLast: '1w',
-            assignees: '__me__',
-            ps: 1,
-            facets: 'createdAt,projectUuids,authors,tags'
-          };
-      return $.get(url, options).done(function (r) {
-        var createdAt = _.findWhere(r.facets, { property: 'createdAt' });
-        return that.model.set({
-          myCreatedAt: createdAt != null ? createdAt.values : null,
-          myProjects: that._getProjects(r),
-          myTags: that._getTags(r)
-        });
-      });
-    },
-
-    _addFormattedValues: function (values) {
-      return values.map(function (v) {
-        var text = window.formatMeasure(v.count, 'SHORT_INT');
-        return _.extend(v, { text: text });
-      });
-    },
-
-    onRender: function () {
-      var values = this.model.get('createdAt'),
-          myValues = this.model.get('myCreatedAt');
-      if (values != null) {
-        this.$('.js-barchart').barchart(this._addFormattedValues(values));
-      }
-      if (myValues != null) {
-        this.$('.js-my-barchart').barchart(this._addFormattedValues(myValues));
-      }
-      this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
-    },
-
-    onDestroy: function () {
-      this.$('[data-toggle="tooltip"]').tooltip('destroy');
-    },
-
-    selectBar: function (e) {
-      var periodStart = $(e.currentTarget).data('period-start'),
-          periodEnd = $(e.currentTarget).data('period-end');
-      this.options.app.state.setQuery({
-        resolved: false,
-        createdAfter: periodStart,
-        createdBefore: periodEnd
-      });
-    },
-
-    selectMyBar: function (e) {
-      var periodStart = $(e.currentTarget).data('period-start'),
-          periodEnd = $(e.currentTarget).data('period-end');
-      this.options.app.state.setQuery({
-        resolved: false,
-        assignees: '__me__',
-        createdAfter: periodStart,
-        createdBefore: periodEnd
-      });
-    },
-
-    serializeData: function () {
-      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
-        user: window.SS.user,
-        filters: _.sortBy(this.options.app.filters.toJSON(), 'name')
-      });
-    }
+    template: Templates['issues-workspace-home']
   });
 
 });
index 557e883f087b0d8204fa40d713286bd5de3a9557..fd1a7ddb74f6f2234388f73c81cd92787d39afe3 100644 (file)
     );
   });
 
+  Handlebars.registerHelper('ifCanUseFilter', function (query, options) {
+    var cond = window.SS.user || query.indexOf('__me__') === -1;
+    return cond ? options.fn(this) : options.inverse(this);
+  });
+
 })();
index cfa2cc22bbaaaa1f3fe63bd338f92fbcde5d88e9..6345b473eba8df8149c40197ecdd5886f2e819a6 100644 (file)
@@ -1,22 +1,3 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
 @import (reference) "../variables";
 @import (reference) "../mixins";
 @import (reference) "../components/ui";
@@ -29,8 +10,7 @@
   &.sticky {
 
     .issues-workspace-list,
-    .issues-workspace-component-viewer,
-    .issues-workspace-home {
+    .issues-workspace-component-viewer {
       padding-top: 22px + 5px + 5px + 1px + 10px;
     }
 
     display: none;
   }
 
+  .issues-header-component,
+  .search-navigator-header-actions {
+    visibility: hidden;
+  }
+
   .issues-workspace-home {
     display: block;
   }
 
 .issues-workspace-home {
   display: none;
-  width: ~"calc(100vw - @{sideWidth})";
-  max-width: 900px;
-  margin-left: auto;
-  margin-right: auto;
-  padding-left: 10px;
-  padding-right: 10px;
-  .box-sizing(border-box);
-  overflow: hidden;
+  padding-top: 180px;
+  text-align: center;
+}
+
+.issues-workspace-home-inner {
+  display: inline-block;
+  vertical-align: top;
+  max-width: 600px;
 }
 
 .issues-facet-mode {
index a4eb6979edd80597c1d835db8eaff2d26cad6e4e..290451d74853976ad13a053b9ed1e3037a61925f 100644 (file)
@@ -7,7 +7,7 @@
       "shared": true,
       "query": "resolved=false",
       "canModify": true,
-      "favourite": false
+      "favorite": false
     },
     {
       "id": "2",
@@ -16,7 +16,7 @@
       "shared": true,
       "query": "resolutions=FALSE-POSITIVE,WONTFIX",
       "canModify": true,
-      "favourite": false
+      "favorite": false
     },
     {
       "id": "3",
@@ -25,7 +25,7 @@
       "shared": true,
       "query": "resolved=false|assignees=__me__",
       "canModify": true,
-      "favourite": false
+      "favorite": false
     }
   ]
 }
index 809f82b7593afb8e71de4e7e806264371761f4ca..767ddee9fc2925376f6ee21ed9a0018439baeee8 100644 (file)
@@ -11,12 +11,9 @@ define(function (require) {
                     .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
                     .mockFromFile('/api/issues/search', 'issues-spec/search.json')
                     .startApp('issues')
-                    .sleep(2000)
-                    .clickElement('.js-new-search')
-                    .checkElementCount('.js-filter', 3)
+                    .checkElementCount('.js-filter', 2)
                     .checkElementCount('.js-filter[data-id="1"]', 1)
-                    .checkElementCount('.js-filter[data-id="2"]', 1)
-                    .checkElementCount('.js-filter[data-id="3"]', 1);
+                    .checkElementCount('.js-filter[data-id="2"]', 1);
             });
 
             bdd.it('should load a saved search', function () {
@@ -27,7 +24,6 @@ define(function (require) {
                     .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
                     .mockFromFile('/api/issues/search', 'issues-spec/search.json')
                     .startApp('issues')
-                    .clickElement('.js-new-search')
                     .clickElement('.search-navigator-filters-show-list')
                     .clickElement('.js-filter[data-id="2"]')
                     .checkElementCount('.js-filter-copy', 1)
@@ -44,7 +40,6 @@ define(function (require) {
                     .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
                     .mockFromFile('/api/issues/search', 'issues-spec/search.json')
                     .startApp('issues')
-                    .clickElement('.js-new-search')
                     .clickElement('.search-navigator-filters-show-list')
                     .clickElement('.js-filter[data-id="2"]')
                     .checkElementCount('.js-filter-copy', 1)
@@ -59,13 +54,12 @@ define(function (require) {
 
         bdd.it('should load', function () {
             return this.remote
-                .open()
+                .open('#resolved=false')
                 .mockFromString('/api/l10n/index', '{}')
                 .mockFromFile('/api/issue_filters/app', 'issues-spec/app.json')
                 .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
                 .mockFromFile('/api/issues/search', 'issues-spec/search.json')
                 .startApp('issues')
-                .clickElement('.js-new-search')
                 .checkElementExist('.facet[data-value=BLOCKER]')
                 .checkElementExist('.facet[data-value=CRITICAL]')
                 .checkElementExist('.facet[data-value=MAJOR]')
@@ -99,13 +93,12 @@ define(function (require) {
 
         bdd.it('should show severity facet', function () {
             return this.remote
-                .open()
+                .open('#resolved=false')
                 .mockFromString('/api/l10n/index', '{}')
                 .mockFromFile('/api/issue_filters/app', 'issues-spec/app.json')
                 .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
                 .mockFromFile('/api/issues/search', 'issues-spec/search.json')
                 .startApp('issues')
-                .clickElement('.js-new-search')
                 .checkElementCount('.issue', 50)
                 .clearMocks()
                 .mockFromFile('/api/issues/search', 'issues-spec/search-reopened.json', { data: { severities: 'BLOCKER' } })
@@ -118,13 +111,12 @@ define(function (require) {
                 issueSelector = '.issue[data-key="' + issueKey + '"]';
 
             return this.remote
-                .open()
+                .open('#resolved=false')
                 .mockFromString('/api/l10n/index', '{}')
                 .mockFromFile('/api/issue_filters/app', 'issues-spec/app.json')
                 .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
                 .mockFromFile('/api/issues/search', 'issues-spec/search.json')
                 .startApp('issues')
-                .clickElement('.js-new-search')
                 .checkElementExist('.js-selection')
                 .checkElementNotExist('.js-selection.icon-checkbox-checked')
                 .checkElementExist('.issue .js-toggle')
@@ -143,7 +135,7 @@ define(function (require) {
 
         bdd.it('should bulk change issues', function () {
             return this.remote
-                .open()
+                .open('#resolved=false')
                 .mockFromString('/api/l10n/index', '{}')
                 .mockFromFile('/api/issue_filters/app', 'issues-spec/app.json')
                 .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
@@ -151,7 +143,6 @@ define(function (require) {
                 .mockFromString('/issues/bulk_change_form*',
                 '<div id="bulk-change-form">bulk change form</div>', { contentType: 'text/plain' })
                 .startApp('issues')
-                .clickElement('.js-new-search')
                 .clickElement('#issues-bulk-change')
                 .clickElement('.js-bulk-change')
                 .checkElementExist('#bulk-change-form')
@@ -163,7 +154,7 @@ define(function (require) {
                 issueSelector = '.issue[data-key="' + issueKey + '"]';
 
             return this.remote
-                .open()
+                .open('#resolved=false')
                 .mockFromString('/api/l10n/index', '{}')
                 .mockFromFile('/api/issue_filters/app', 'issues-spec/app.json')
                 .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
@@ -171,7 +162,6 @@ define(function (require) {
                 .mockFromString('/issues/bulk_change_form*',
                 '<div id="bulk-change-form">bulk change form</div>', { contentType: 'text/plain' })
                 .startApp('issues')
-                .clickElement('.js-new-search')
                 .checkElementExist('.js-selection')
                 .checkElementNotExist('.js-selection.icon-checkbox-checked')
                 .checkElementExist('.issue .js-toggle')
@@ -194,13 +184,12 @@ define(function (require) {
 
         bdd.it('should filter similar issues', function () {
             return this.remote
-                .open()
+                .open('#resolved=false')
                 .mockFromString('/api/l10n/index', '{}')
                 .mockFromFile('/api/issue_filters/app', 'issues-spec/app.json')
                 .mockFromFile('/api/issue_filters/search', 'issues-spec/issue-filters.json')
                 .mockFromFile('/api/issues/search', 'issues-spec/search-filter-similar-issues.json')
                 .startApp('issues')
-                .clickElement('.js-new-search')
                 .checkElementCount('.issue', 2)
                 .clickElement('.issue.selected .js-issue-filter')
                 .checkElementExist('.bubble-popup')