]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4394 Provide ability to share issue filters
authorJulien Lancelot <julien.lancelot@gmail.com>
Tue, 18 Jun 2013 16:54:40 +0000 (18:54 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Tue, 18 Jun 2013 16:54:40 +0000 (18:54 +0200)
24 files changed:
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-core/src/main/java/org/sonar/core/issue/db/IssueFilterDao.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueFilterDto.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueFilterFavouriteDao.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueFilterFavouriteMapper.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueFilterMapper.java
sonar-core/src/main/java/org/sonar/core/user/AuthorizationDao.java
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueFilterFavouriteMapper.xml
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueFilterMapper.xml
sonar-core/src/main/resources/org/sonar/core/user/AuthorizationMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/db/IssueFilterDaoTest.java
sonar-core/src/test/java/org/sonar/core/issue/db/IssueFilterFavouriteDaoTest.java
sonar-core/src/test/java/org/sonar/core/user/AuthorizationDaoTest.java
sonar-core/src/test/resources/org/sonar/core/issue/db/IssueFilterFavouriteDaoTest/should_delete_by_issue_filter_id-result.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/user/AuthorizationDaoTest/should_return_global_permissions.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
sonar-server/src/main/java/org/sonar/server/issue/IssueFilterService.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/views/issues/_action_links.html.erb [deleted file]
sonar-server/src/main/webapp/WEB-INF/app/views/issues/_shared_form.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/issues/manage.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb
sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java
sonar-server/src/test/java/org/sonar/server/issue/IssueFilterServiceTest.java

index bc3dc9e4f679d22e64210a0ed23a024a793a2a95..fc6c3b895adcc0d5be1a61550eeed59b1a56509d 100644 (file)
@@ -541,8 +541,10 @@ issue_filter.manage.my_filters=My Filters
 issue_filter.no_filters=No filters
 issue_filter.delete_confirm_title=Delete Filter
 issue_filter.are_you_sure_want_delete_filter_x=Are you sure that you want to delete the filter "{0}"?
-
-
+issue_filter.private=Private
+issue_filter.shared_with_all_users=Shared with all users
+issue_filter.sharing=Sharing
+issue_filter.manage.shared_filters=Shared Filters
 
 #------------------------------------------------------------------------------
 #
index d74fafd02aed132c6d4267cd75fde4f19cc88ca1..b4ffcaeb88064bbba4c67902e6b15a8e7ceefaef 100644 (file)
@@ -80,6 +80,15 @@ public class IssueFilterDao implements BatchComponent, ServerComponent {
     }
   }
 
+  public List<IssueFilterDto> selectSharedForUser(String user) {
+    SqlSession session = mybatis.openSession();
+    try {
+      return getMapper(session).selectSharedForUser(user);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
   public void insert(IssueFilterDto filter) {
     SqlSession session = mybatis.openSession();
     try {
index ac366e66a1f0d510c08fa5964501a0efc79dddd6..5a831c285fad5215e2a6668f80186cd3956c8ad4 100644 (file)
@@ -33,7 +33,7 @@ public class IssueFilterDto {
   private Long id;
   private String name;
   private String userLogin;
-  private Boolean shared;
+  private boolean shared;
   private String description;
   private String data;
   private Date createdAt;
index 2f692c1b9671718aad8ba2267014567aa64d7e9b..17e2b17b1dede8af4d44dae3bf4df4d79e7f5bae 100644 (file)
@@ -64,10 +64,20 @@ public class IssueFilterFavouriteDao implements BatchComponent, ServerComponent
     }
   }
 
-  public void delete(Long id) {
+  public void delete(Long issueFilterFavouriteId) {
     SqlSession session = mybatis.openSession();
     try {
-      getMapper(session).delete(id);
+      getMapper(session).delete(issueFilterFavouriteId);
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public void deleteByIssueFilterId(Long issueFilterId) {
+    SqlSession session = mybatis.openSession();
+    try {
+      getMapper(session).deleteByIssueFilterId(issueFilterId);
       session.commit();
     } finally {
       MyBatis.closeQuietly(session);
index 03986f821180b1f54a990040ca50f40c79b4961b..d11fc9d1ca1bd10e3a27cd69a7f3b8c0cf928ca3 100644 (file)
@@ -36,5 +36,7 @@ public interface IssueFilterFavouriteMapper {
 
   void insert(IssueFilterFavouriteDto filterFavourite);
 
-  void delete(Long id);
+  void delete(Long issueFilterFavouriteId);
+
+  void deleteByIssueFilterId(Long issueFilterId);
 }
index f07316f07a14b1af6e4aaa059473f5df52014044..53cf2b936eb58d5c33bb33dbb16a46eb5b3d908b 100644 (file)
@@ -41,6 +41,8 @@ public interface IssueFilterMapper {
 
   List<IssueFilterDto> selectByUserWithOnlyFavoriteFilters(String user);
 
+  List<IssueFilterDto> selectSharedForUser(String user);
+
   void insert(IssueFilterDto filter);
 
   void update(IssueFilterDto filter);
index 90f0fcefb69ba1155041b86bce37b301a422e370..7a826417df011c57723eb9bcf67aebeb2df292b6 100644 (file)
@@ -27,10 +27,7 @@ import org.sonar.core.persistence.MyBatis;
 
 import javax.annotation.Nullable;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import static com.google.common.collect.Maps.newHashMap;
 
@@ -92,4 +89,15 @@ public class AuthorizationDao implements ServerComponent {
 
     return session.selectList(sql, params);
   }
+
+  public List<String> selectGlobalPermissions(String userLogin){
+    SqlSession session = mybatis.openSession();
+    try {
+      Map<String, Object> params = newHashMap();
+      params.put("userLogin", userLogin);
+      return session.selectList("selectGlobalPermissions", params);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
 }
index 3c8d2db4718ea8d3a48548729bd147a727434d3f..80c36ec8c09f7fadfad83ff69bb2f10c75cb9728 100644 (file)
@@ -46,4 +46,8 @@
     delete from issue_filter_favourites where id=#{id}
   </delete>
 
+  <delete id="deleteByIssueFilterId" parameterType="int">
+    delete from issue_filter_favourites where issue_filter_id=#{issueFilterId}
+  </delete>
+
 </mapper>
index 2d62e89af3ddf4adf52297eecd3309401002f841..a31b00273c52aeeac44f7b3263b5d6087e57df45 100644 (file)
     </where>
   </select>
 
+  <select id="selectSharedForUser" parameterType="String" resultType="IssueFilter">
+    select <include refid="issueFilterColumns"/>
+    from issue_filters filters
+    <where>
+      filters.shared=${_true}
+      and filters.user_login&lt;&gt;#{user}
+    </where>
+  </select>
+
   <insert id="insert" parameterType="IssueFilter" useGeneratedKeys="true" keyProperty="id">
     INSERT INTO issue_filters (name, user_login, shared, description, data, created_at, updated_at)
     VALUES (#{name}, #{userLogin}, #{shared}, #{description}, #{data}, #{createdAt}, #{updatedAt})
index 88677e1dd941a0391eebca05c38122307de97eae..c872b7274ac025caf909ac272c97ad10d01c51c7 100644 (file)
     </choose>
   </sql>
 
+  <select id="selectGlobalPermissions" parameterType="map" resultType="String">
+    SELECT gr.role
+    FROM group_roles gr
+    inner join groups_users gu on gu.group_id=gr.id
+    inner join users u on u.id=gu.user_id
+    <where>
+      u.login=#{userLogin}
+      and gr.resource_id  is null
+    </where>
+  </select>
+
 </mapper>
index 514a7b29d7c75e9e12402cf3d5412aa5ae8bd437..dd832ecf550873adb1aeeeadd12307eaaf2c229f 100644 (file)
@@ -81,6 +81,14 @@ public class IssueFilterDaoTest extends AbstractDaoTestCase {
     assertThat(issueFilterDto.getId()).isEqualTo(2L);
   }
 
+  @Test
+  public void should_select_shared() {
+    setupData("shared");
+
+    assertThat(dao.selectSharedForUser("michael")).hasSize(1);
+    assertThat(dao.selectSharedForUser("stephane")).isEmpty();
+  }
+
   @Test
   public void should_insert() {
     setupData("shared");
index 4cdaa9de69e5eb2a503c77478a76d28c24466462..c41e1ab52a98d7f051587de757fccb5893f4c374 100644 (file)
@@ -83,4 +83,13 @@ public class IssueFilterFavouriteDaoTest extends AbstractDaoTestCase {
     checkTables("should_delete", new String[]{"created_at"}, "issue_filter_favourites");
   }
 
+  @Test
+  public void should_delete_by_issue_filter_id() {
+    setupData("shared");
+
+    dao.deleteByIssueFilterId(10l);
+
+    checkTables("should_delete_by_issue_filter_id", new String[]{"created_at"}, "issue_filter_favourites");
+  }
+
 }
index 42746971ac13a1f7933f7366b120841c02a0c26f..df198a6028057b78f8c90f7bba2b546ca491b648 100644 (file)
@@ -150,4 +150,14 @@ public class AuthorizationDaoTest extends AbstractDaoTestCase {
     rootProjectIds = authorization.selectAuthorizedRootProjectsIds(null, "admin");
     assertThat(rootProjectIds).isEmpty();
   }
+
+  @Test
+  public void should_return_global_permissions() {
+    setupData("should_return_global_permissions");
+
+    AuthorizationDao authorization = new AuthorizationDao(getMyBatis());
+    assertThat(authorization.selectGlobalPermissions("john")).containsOnly("user", "admin");
+    assertThat(authorization.selectGlobalPermissions("arthur")).containsOnly("user");
+    assertThat(authorization.selectGlobalPermissions("none")).isEmpty();
+  }
 }
diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueFilterFavouriteDaoTest/should_delete_by_issue_filter_id-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueFilterFavouriteDaoTest/should_delete_by_issue_filter_id-result.xml
new file mode 100644 (file)
index 0000000..e1df29f
--- /dev/null
@@ -0,0 +1,9 @@
+<dataset>
+
+  <issue_filter_favourites
+      id="2"
+      user_login="stephane"
+      issue_filter_id="11"
+      created_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/user/AuthorizationDaoTest/should_return_global_permissions.xml b/sonar-core/src/test/resources/org/sonar/core/user/AuthorizationDaoTest/should_return_global_permissions.xml
new file mode 100644 (file)
index 0000000..863d395
--- /dev/null
@@ -0,0 +1,13 @@
+<dataset>
+
+  <users id="1" login="john" />
+  <users id="2" login="arthur" />
+
+  <groups_users user_id="1" group_id="200"/>
+  <groups_users user_id="1" group_id="201"/>
+  <groups_users user_id="2" group_id="200"/>
+
+  <group_roles id="200" group_id="200" resource_id="[null]" role="user"/>
+  <group_roles id="201" group_id="200" resource_id="[null]" role="admin"/>
+
+</dataset>
index e44e0bffd6cad16017f8d2c86511e98729b93564..50611a9dd3995c55dce189686320fc0d71199935 100644 (file)
@@ -502,6 +502,10 @@ public class InternalRubyIssueService implements ServerComponent {
     return result;
   }
 
+  public List<DefaultIssueFilter> findSharedFiltersForCurrentUser() {
+    return issueFilterService.findSharedFilters(UserSession.get());
+  }
+
   public List<DefaultIssueFilter> findFavouriteIssueFiltersForCurrentUser() {
     return issueFilterService.findFavoriteFilters(UserSession.get());
   }
index 8de1a6da8362a7230516b29e969e88a901157e8d..8be218777e8b15d6cba00c0a885cc26da2970a68 100644 (file)
@@ -26,11 +26,13 @@ import org.sonar.api.ServerComponent;
 import org.sonar.api.issue.IssueFinder;
 import org.sonar.api.issue.IssueQuery;
 import org.sonar.api.issue.IssueQueryResult;
+import org.sonar.api.web.UserRole;
 import org.sonar.core.issue.DefaultIssueFilter;
 import org.sonar.core.issue.db.IssueFilterDao;
 import org.sonar.core.issue.db.IssueFilterDto;
 import org.sonar.core.issue.db.IssueFilterFavouriteDao;
 import org.sonar.core.issue.db.IssueFilterFavouriteDto;
+import org.sonar.core.user.AuthorizationDao;
 import org.sonar.server.user.UserSession;
 
 import javax.annotation.CheckForNull;
@@ -46,11 +48,13 @@ public class IssueFilterService implements ServerComponent {
   private final IssueFilterDao issueFilterDao;
   private final IssueFilterFavouriteDao issueFilterFavouriteDao;
   private final IssueFinder issueFinder;
+  private final AuthorizationDao authorizationDao;
 
-  public IssueFilterService(IssueFilterDao issueFilterDao, IssueFilterFavouriteDao issueFilterFavouriteDao, IssueFinder issueFinder) {
+  public IssueFilterService(IssueFilterDao issueFilterDao, IssueFilterFavouriteDao issueFilterFavouriteDao, IssueFinder issueFinder, AuthorizationDao authorizationDao) {
     this.issueFilterDao = issueFilterDao;
     this.issueFilterFavouriteDao = issueFilterFavouriteDao;
     this.issueFinder = issueFinder;
+    this.authorizationDao = authorizationDao;
   }
 
   public IssueQueryResult execute(IssueQuery issueQuery) {
@@ -73,13 +77,7 @@ public class IssueFilterService implements ServerComponent {
 
   public List<DefaultIssueFilter> findByUser(UserSession userSession) {
     if (userSession.isLoggedIn() && userSession.login() != null) {
-      List<IssueFilterDto> issueFilterDtoList = issueFilterDao.selectByUser(userSession.login());
-      return newArrayList(Iterables.transform(issueFilterDtoList, new Function<IssueFilterDto, DefaultIssueFilter>() {
-        @Override
-        public DefaultIssueFilter apply(IssueFilterDto issueFilterDto) {
-          return issueFilterDto.toIssueFilter();
-        }
-      }));
+      return toIssueFilters(issueFilterDao.selectByUser(userSession.login()));
     }
     return Collections.emptyList();
   }
@@ -88,15 +86,12 @@ public class IssueFilterService implements ServerComponent {
     verifyLoggedIn(userSession);
     issueFilter.setUser(userSession.login());
     verifyNameIsNotAlreadyUsed(issueFilter, userSession);
-
-    IssueFilterDto issueFilterDto = IssueFilterDto.toIssueFilter(issueFilter);
-    issueFilterDao.insert(issueFilterDto);
-    addFavouriteIssueFilter(issueFilterDto.getId(), userSession.login());
-    return issueFilterDto.toIssueFilter();
+    return insertIssueFilter(issueFilter, userSession.login());
   }
 
   public DefaultIssueFilter update(DefaultIssueFilter issueFilter, UserSession userSession) {
-    findIssueFilter(issueFilter.id(), userSession);
+    IssueFilterDto issueFilterDto = findIssueFilter(issueFilter.id(), userSession);
+    verifyCurrentUserCanModifyFilter(issueFilterDto, userSession);
     verifyNameIsNotAlreadyUsed(issueFilter, userSession);
 
     issueFilterDao.update(IssueFilterDto.toIssueFilter(issueFilter));
@@ -105,6 +100,8 @@ public class IssueFilterService implements ServerComponent {
 
   public DefaultIssueFilter updateData(Long issueFilterId, Map<String, Object> mapData, UserSession userSession) {
     IssueFilterDto issueFilterDto = findIssueFilter(issueFilterId, userSession);
+    verifyCurrentUserCanModifyFilter(issueFilterDto, userSession);
+
     DefaultIssueFilter issueFilter = issueFilterDto.toIssueFilter();
     issueFilter.setData(mapData);
     issueFilterDao.update(IssueFilterDto.toIssueFilter(issueFilter));
@@ -112,11 +109,10 @@ public class IssueFilterService implements ServerComponent {
   }
 
   public void delete(Long issueFilterId, UserSession userSession) {
-    findIssueFilter(issueFilterId, userSession);
-    IssueFilterFavouriteDto issueFilterFavouriteDto = findFavouriteIssueFilter(userSession.login(), issueFilterId);
-    if (issueFilterFavouriteDto != null) {
-      deleteFavouriteIssueFilter(issueFilterFavouriteDto.getId());
-    }
+    IssueFilterDto issueFilterDto = findIssueFilter(issueFilterId, userSession);
+    verifyCurrentUserCanModifyFilter(issueFilterDto, userSession);
+
+    deleteFavouriteIssueFilters(issueFilterDto);
     issueFilterDao.delete(issueFilterId);
   }
 
@@ -126,20 +122,19 @@ public class IssueFilterService implements ServerComponent {
     issueFilter.setData(issueFilterDtoToCopy.getData());
     verifyNameIsNotAlreadyUsed(issueFilter, userSession);
 
-    IssueFilterDto issueFilterDto = IssueFilterDto.toIssueFilter(issueFilter);
-    issueFilterDao.insert(issueFilterDto);
-    return issueFilterDto.toIssueFilter();
+    return insertIssueFilter(issueFilter, userSession.login());
+  }
+
+  public List<DefaultIssueFilter> findSharedFilters(UserSession userSession) {
+    if (userSession.isLoggedIn() && userSession.login() != null) {
+      return toIssueFilters(issueFilterDao.selectSharedForUser(userSession.login()));
+    }
+    return Collections.emptyList();
   }
 
   public List<DefaultIssueFilter> findFavoriteFilters(UserSession userSession) {
     if (userSession.isLoggedIn() && userSession.login() != null) {
-      List<IssueFilterDto> issueFilterDtoList = issueFilterDao.selectByUserWithOnlyFavoriteFilters(userSession.login());
-      return newArrayList(Iterables.transform(issueFilterDtoList, new Function<IssueFilterDto, DefaultIssueFilter>() {
-        @Override
-        public DefaultIssueFilter apply(IssueFilterDto issueFilterDto) {
-          return issueFilterDto.toIssueFilter();
-        }
-      }));
+      return toIssueFilters(issueFilterDao.selectByUserWithOnlyFavoriteFilters(userSession.login()));
     }
     return Collections.emptyList();
   }
@@ -150,18 +145,18 @@ public class IssueFilterService implements ServerComponent {
     if (issueFilterFavouriteDto == null) {
       addFavouriteIssueFilter(issueFilterId, userSession.login());
     } else {
-      deleteFavouriteIssueFilter(issueFilterFavouriteDto.getId());
+      deleteFavouriteIssueFilter(issueFilterFavouriteDto);
     }
   }
 
-  public IssueFilterDto findIssueFilter(Long id, UserSession userSession){
+  public IssueFilterDto findIssueFilter(Long id, UserSession userSession) {
     verifyLoggedIn(userSession);
     IssueFilterDto issueFilterDto = issueFilterDao.selectById(id);
     if (issueFilterDto == null) {
       // TODO throw 404
       throw new IllegalArgumentException("Filter not found: " + id);
     }
-    verifyCurrentUserIsOwnerOfFilter(issueFilterDto, userSession);
+    verifyCurrentUserCanReadFilter(issueFilterDto, userSession);
     return issueFilterDto;
   }
 
@@ -171,14 +166,21 @@ public class IssueFilterService implements ServerComponent {
     }
   }
 
-  private void verifyCurrentUserIsOwnerOfFilter(IssueFilterDto issueFilterDto, UserSession userSession){
-    if (!issueFilterDto.getUserLogin().equals(userSession.login())) {
+  private void verifyCurrentUserCanReadFilter(IssueFilterDto issueFilterDto, UserSession userSession) {
+    if (!issueFilterDto.getUserLogin().equals(userSession.login()) && !issueFilterDto.isShared()) {
+      // TODO throw unauthorized
+      throw new IllegalStateException("User is not authorized to read this filter");
+    }
+  }
+
+  private void verifyCurrentUserCanModifyFilter(IssueFilterDto issueFilterDto, UserSession userSession) {
+    if (!issueFilterDto.getUserLogin().equals(userSession.login()) && (!issueFilterDto.isShared() || !isAdmin(userSession.login()))) {
       // TODO throw unauthorized
-      throw new IllegalStateException("User is not authorized to get this filter");
+      throw new IllegalStateException("User is not authorized to modify this filter");
     }
   }
 
-  private void verifyNameIsNotAlreadyUsed(DefaultIssueFilter issueFilter, UserSession userSession){
+  private void verifyNameIsNotAlreadyUsed(DefaultIssueFilter issueFilter, UserSession userSession) {
     if (issueFilterDao.selectByNameAndUser(issueFilter.name(), userSession.login(), issueFilter.id()) != null) {
       throw new IllegalArgumentException("Name already exists");
     }
@@ -195,8 +197,32 @@ public class IssueFilterService implements ServerComponent {
     issueFilterFavouriteDao.insert(issueFilterFavouriteDto);
   }
 
-  private void deleteFavouriteIssueFilter(Long issueFilterFavoriteId) {
-    issueFilterFavouriteDao.delete(issueFilterFavoriteId);
+  private void deleteFavouriteIssueFilter(IssueFilterFavouriteDto issueFilterFavouriteDto) {
+    issueFilterFavouriteDao.delete(issueFilterFavouriteDto.getId());
+  }
+
+  private void deleteFavouriteIssueFilters(IssueFilterDto issueFilterDto) {
+    issueFilterFavouriteDao.deleteByIssueFilterId(issueFilterDto.getId());
+  }
+
+  private DefaultIssueFilter insertIssueFilter(DefaultIssueFilter issueFilter, String user) {
+    IssueFilterDto issueFilterDto = IssueFilterDto.toIssueFilter(issueFilter);
+    issueFilterDao.insert(issueFilterDto);
+    addFavouriteIssueFilter(issueFilterDto.getId(), user);
+    return issueFilterDto.toIssueFilter();
+  }
+
+  private List<DefaultIssueFilter> toIssueFilters(List<IssueFilterDto> issueFilterDtoList) {
+    return newArrayList(Iterables.transform(issueFilterDtoList, new Function<IssueFilterDto, DefaultIssueFilter>() {
+      @Override
+      public DefaultIssueFilter apply(IssueFilterDto issueFilterDto) {
+        return issueFilterDto.toIssueFilter();
+      }
+    }));
+  }
+
+  private boolean isAdmin(String user) {
+    return authorizationDao.selectGlobalPermissions(user).contains(UserRole.ADMIN);
   }
 
 }
index d3d4500aed8aa49b08f89e4c72e5e59ed86d0ff6..2d05a10d4d97665ff6d9797f720874169bd51097 100644 (file)
@@ -57,6 +57,7 @@ class IssuesController < ApplicationController
   def manage
     @issue_query = Internal.issues.toQuery({})
     @filters = Internal.issues.findIssueFiltersForCurrentUser()
+    @shared_filters = Internal.issues.findSharedFiltersForCurrentUser()
     @favourite_filter_ids = @favourite_filters.map { |filter| filter.id }
   end
 
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_action_links.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_action_links.html.erb
deleted file mode 100644 (file)
index 127732b..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<% if logged_in? && !@criteria_params.empty? %>
-<ul class="operations">
-  <% if @filter.id %>
-    <li><a id="copy" href="<%= url_for :action => 'copy_form', :id => @filter.id -%>" class="link-action open-modal"><%= message('copy') -%></a></li>
-  <% end %>
-  <% if !defined?(@unchanged) && @filter.id && @filter.user == current_user.login %>
-    <li>
-      <%= link_to message('save'), params.merge({:action => 'save', :id => @filter.id}), :class => 'link-action', :id => 'save', :method => :post -%>
-    </li>
-  <% end %>
-  <% unless @filter.id %>
-    <li>
-      <a id="save-as" href="<%= url_for params.merge({:action => 'save_as_form', :id => @filter.id}) -%>" class="link-action open-modal"><%= message('save_as') -%></a>
-    </li>
-  <% end %>
-</ul>
-<% end %>
\ No newline at end of file
index 45f1bb7599bfd7d465d0c57e1d1f6f8ffefa40e5..695f50a8ff7e3dc474593b6c75e66b77e36708f2 100644 (file)
@@ -8,8 +8,7 @@
     <label for="description"><%= message('issue_filter.form.description') -%></label>
     <input id="description" name="description" type="text" size="50" maxlength="4000" value="<%= params[:description] || h(@filter.description) -%>"/>
   </div>
-  <!--TODO-->
-  <div class="modal-field hidden">
+  <div class="modal-field">
     <label for="shared"><%= message('issue_filter.form.share') -%></label>
     <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if (params[:shared] || (@filter && @filter.shared)) -%>/>
   </div>
index 9c2baa799bce792444c9578491b3fb2b70572363..766f78756b57f16da87ca2850f5cf154f0abcaa5 100644 (file)
@@ -27,7 +27,8 @@
         <thead>
         <tr>
           <th class="thin"></th>
-          <th><%= message('name') -%></th>
+          <th><%= message('issue_filter.form.name') -%></th>
+          <th><%= message('issue_filter.sharing') -%></th>
           <th class="right"><%= message('operations') -%></th>
         </tr>
         </thead>
                   <div class="note"><%= h filter.description -%></div>
                 <% end %>
               </td>
+              <td>
+                <% if filter.shared %>
+                  <%= message 'issue_filter.shared_with_all_users' -%>
+                <% else %>
+                  <%= message 'issue_filter.private' -%>
+                <% end %>
+              </td>
               <td class="thin nowrap right">
-                <!--TODO copy-->
-                <a class="hidden" id="copy-<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/copy_form/<%= filter.id -%>"
+                <a id="copy-<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/copy_form/<%= filter.id -%>"
                    class="link-action open-modal"><%= message('copy') -%></a>
                 &nbsp;
                 <a id="edit_<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/edit_form/<%= filter.id -%>"
         </tbody>
       </table>
 
+      <h1><%= message 'issue_filter.manage.shared_filters' -%></h1>
+      <table class="data" id="shared-filters">
+        <thead>
+        <tr>
+          <th class="thin"></th>
+          <th><%= message('issue_filter.form.name') -%></th>
+          <th><%= message('shared_by') -%></th>
+          <th class="right"><%= message('operations') -%></th>
+        </tr>
+        </thead>
+        <tbody>
+        <% if @shared_filters.empty? %>
+          <tr class="even">
+            <td colspan="4"><%= message('issue_filter.no_filters') -%></td>
+          </tr>
+        <% else %>
+          <% @shared_filters.each do |filter| %>
+            <tr id="shared-<%= filter.name.parameterize -%>" class="<%= cycle('even', 'odd', :name => 'shared-filters') -%>">
+              <td>
+                <%= issue_filter_star(filter, @favourite_filter_ids.include?(filter.id)) -%>
+              </td>
+              <td>
+                <%= link_to h(filter.name), :action => 'filter', :id => filter.id -%>
+                <% if filter.description %>
+                  <div class="note"><%= h filter.description -%></div>
+                <% end %>
+              </td>
+              <td>
+                <%= h Api.users.findByLogin(filter.user).name -%>
+              </td>
+              <td class="thin nowrap right">
+                <a id="copy-<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/copy_form/<%= filter.id -%>" class="link-action open-modal"><%= message('copy') -%></a>
+                <% if has_role?(:admin) %>
+                  &nbsp;
+                  <a id="edit_system_<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/edit_form/<%= filter.id -%>" class="link-action open-modal"><%= message('edit') -%></a>
+                  &nbsp;
+                  <%= link_to_action message('delete'), "#{ApplicationController.root_context}/issues/delete/#{filter.id}",
+                                     :class => 'link-action link-red',
+                                     :id => "delete_system_#{filter.name.parameterize}",
+                                     :confirm_button => message('delete'),
+                                     :confirm_title => 'issue_filter.delete_confirm_title',
+                                     :confirm_msg => 'issue_filter.are_you_sure_want_delete_filter_x',
+                                     :confirm_msg_params => [filter.name] -%>
+                <% end %>
+              </td>
+            </tr>
+          <% end %>
+        <% end %>
+        </tbody>
+      </table>
+
     </div>
   </div>
 </div>
\ No newline at end of file
index 48f0fbb4f4239857ae8cc6ce2f8bbc8b27512ad6..4e7b9f1652c211e0b3c632c3d53c503ed2845e13 100644 (file)
@@ -8,7 +8,43 @@
       <%= render :partial => 'display_errors' %>
 
       <div class="line-block marginbottom10">
-        <%= render :partial => 'action_links' -%>
+        <% if logged_in? && !@criteria_params.empty? %>
+          <ul class="operations">
+            <% if @filter.id %>
+              <li><a id="copy" href="<%= url_for :action => 'copy_form', :id => @filter.id -%>" class="link-action open-modal"><%= message('copy') -%></a></li>
+            <% end %>
+            <% if !defined?(@unchanged) && @filter.id && @filter.user == current_user.login %>
+              <li>
+                <%= link_to message('save'), params.merge({:action => 'save', :id => @filter.id}), :class => 'link-action', :id => 'save', :method => :post -%>
+              </li>
+            <% end %>
+            <% unless @filter.id %>
+              <li>
+                <a id="save-as" href="<%= url_for params.merge({:action => 'save_as_form', :id => @filter.id}) -%>" class="link-action open-modal"><%= message('save_as') -%></a>
+              </li>
+            <% end %>
+          </ul>
+
+          <div class="page_title" id="filter-title">
+            <% if @filter.id && @filter.name.present? %>
+              <p>
+                <span class="h3"><%= h @filter.name -%></span>
+                <span class="note">
+                  <% if !@filter.shared %>
+                    [<%= message 'issue_filter.private' -%>]
+                  <% elsif @filter.user==current_user.login %>
+                    [<%= message 'issue_filter.shared_with_all_users' -%>]
+                  <% elsif @filter.user %>
+                    [<%= message 'shared_by' -%> <%= Api.users.findByLogin(@filter.user).name -%>]
+                  <% end %>
+                </span>
+                <% if @filter.user == current_user.login %>
+                  &nbsp;<a href="<%= url_for :action => 'edit_form', :id => @filter.id -%>" class="open-modal" id="edit-filter"><%= image_tag 'pencil-small.png', :alt => message('edit') -%></a>
+                <% end %>
+              </p>
+            <% end %>
+          </div>
+        <% end %>
       </div>
 
       <% if @issues_result && @issues_result.maxResultsReached() %>
index 8f82db7347a4cc8bfb8308d29d907745399f7d44..30a501842f465129773d578281ef097dc109fe0f 100644 (file)
@@ -322,6 +322,13 @@ public class InternalRubyIssueServiceTest {
     assertThat(issueFilter.description()).isEqualTo("Long term issues");
   }
 
+  @Test
+  public void should_update_data() {
+    Map<String, Object> data = newHashMap();
+    service.updateIssueFilterData(10L, data);
+    verify(issueFilterService).updateData(eq(10L), eq(data), any(UserSession.class));
+  }
+
   @Test
   public void should_delete_issue_filter() {
     Result<DefaultIssueFilter> result = service.deleteIssueFilter(1L);
@@ -408,10 +415,9 @@ public class InternalRubyIssueServiceTest {
   }
 
   @Test
-  public void should_update_data() {
-    Map<String, Object> data = newHashMap();
-    service.updateIssueFilterData(10L, data);
-    verify(issueFilterService).updateData(eq(10L), eq(data), any(UserSession.class));
+  public void should_find_shared_issue_filters() {
+    service.findSharedFiltersForCurrentUser();
+    verify(issueFilterService).findSharedFilters(any(UserSession.class));
   }
 
   @Test
index a1e1aa0e17994e6b06c27231672879c3e12b7382..5a2aad576cd419f74fbe08d974b3354802927718 100644 (file)
@@ -25,11 +25,13 @@ import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.sonar.api.issue.IssueFinder;
 import org.sonar.api.issue.IssueQuery;
+import org.sonar.api.web.UserRole;
 import org.sonar.core.issue.DefaultIssueFilter;
 import org.sonar.core.issue.db.IssueFilterDao;
 import org.sonar.core.issue.db.IssueFilterDto;
 import org.sonar.core.issue.db.IssueFilterFavouriteDao;
 import org.sonar.core.issue.db.IssueFilterFavouriteDto;
+import org.sonar.core.user.AuthorizationDao;
 import org.sonar.server.user.UserSession;
 
 import java.util.List;
@@ -49,6 +51,7 @@ public class IssueFilterServiceTest {
   private IssueFilterDao issueFilterDao;
   private IssueFilterFavouriteDao issueFilterFavouriteDao;
   private IssueFinder issueFinder;
+  private AuthorizationDao authorizationDao;
 
   private UserSession userSession;
 
@@ -62,8 +65,9 @@ public class IssueFilterServiceTest {
     issueFilterDao = mock(IssueFilterDao.class);
     issueFilterFavouriteDao = mock(IssueFilterFavouriteDao.class);
     issueFinder = mock(IssueFinder.class);
+    authorizationDao = mock(AuthorizationDao.class);
 
-    service = new IssueFilterService(issueFilterDao, issueFilterFavouriteDao, issueFinder);
+    service = new IssueFilterService(issueFilterDao, issueFilterFavouriteDao, issueFinder, authorizationDao);
   }
 
   @Test
@@ -76,6 +80,16 @@ public class IssueFilterServiceTest {
     assertThat(issueFilter.id()).isEqualTo(1L);
   }
 
+  @Test
+  public void should_find_shared_filter_by_id() {
+    IssueFilterDto issueFilterDto = new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("arthur").setShared(true);
+    when(issueFilterDao.selectById(1L)).thenReturn(issueFilterDto);
+
+    DefaultIssueFilter issueFilter = service.findById(1L, userSession);
+    assertThat(issueFilter).isNotNull();
+    assertThat(issueFilter.id()).isEqualTo(1L);
+  }
+
   @Test
   public void should_not_find_by_id_on_not_existing_issue() {
     when(issueFilterDao.selectById(1L)).thenReturn(null);
@@ -101,15 +115,15 @@ public class IssueFilterServiceTest {
   }
 
   @Test
-  public void should_not_find_if_user_is_not_the_owner_of_filter() {
-    IssueFilterDto issueFilterDto = new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("eric");
+  public void should_not_find_if_not_shared_and_user_is_not_the_owner_of_filter() {
+    IssueFilterDto issueFilterDto = new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("eric").setShared(false);
     when(issueFilterDao.selectById(1L)).thenReturn(issueFilterDto);
     try {
       // John is not authorized to see eric filters
       service.findById(1L, userSession);
       fail();
     } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not authorized to get this filter");
+      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not authorized to read this filter");
     }
   }
 
@@ -184,6 +198,17 @@ public class IssueFilterServiceTest {
     verify(issueFilterDao).update(any(IssueFilterDto.class));
   }
 
+  @Test
+  public void should_update_shared_filter_if_admin() {
+    when(authorizationDao.selectGlobalPermissions("john")).thenReturn(newArrayList(UserRole.ADMIN));
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Old Filter").setUserLogin("arthur").setShared(true));
+
+    DefaultIssueFilter result = service.update(new DefaultIssueFilter().setId(1L).setName("My New Filter"), userSession);
+    assertThat(result.name()).isEqualTo("My New Filter");
+
+    verify(issueFilterDao).update(any(IssueFilterDto.class));
+  }
+
   @Test
   public void should_not_update_if_filter_not_found() {
     when(issueFilterDao.selectById(1L)).thenReturn(null);
@@ -197,6 +222,20 @@ public class IssueFilterServiceTest {
     verify(issueFilterDao, never()).update(any(IssueFilterDto.class));
   }
 
+  @Test
+  public void should_not_update_if_shared_and_not_admin() {
+    when(authorizationDao.selectGlobalPermissions("john")).thenReturn(newArrayList(UserRole.USER));
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Old Filter").setUserLogin("arthur").setShared(true));
+
+    try {
+      service.update(new DefaultIssueFilter().setId(1L).setName("My New Filter"), userSession);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not authorized to modify this filter");
+    }
+    verify(issueFilterDao, never()).update(any(IssueFilterDto.class));
+  }
+
   @Test
   public void should_not_update_if_name_already_used() {
     when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Old Filter").setUserLogin("john"));
@@ -241,7 +280,7 @@ public class IssueFilterServiceTest {
     service.delete(1L, userSession);
 
     verify(issueFilterDao).delete(1L);
-    verify(issueFilterFavouriteDao).delete(10L);
+    verify(issueFilterFavouriteDao).deleteByIssueFilterId(1L);
   }
 
   @Test
@@ -257,6 +296,44 @@ public class IssueFilterServiceTest {
     verify(issueFilterDao, never()).delete(anyLong());
   }
 
+  @Test
+  public void should_delete_shared_filter_if_user_is_admin() {
+    when(authorizationDao.selectGlobalPermissions("john")).thenReturn(newArrayList(UserRole.ADMIN));
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("arthur").setShared(true));
+
+    service.delete(1L, userSession);
+
+    verify(issueFilterDao).delete(1L);
+  }
+
+  @Test
+  public void should_not_delete_not_shared_filter_if_user_is_admin() {
+    when(authorizationDao.selectGlobalPermissions("john")).thenReturn(newArrayList(UserRole.ADMIN));
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("arthur").setShared(false));
+
+    try {
+      service.delete(1L, userSession);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not authorized to read this filter");
+    }
+    verify(issueFilterDao, never()).delete(anyLong());
+  }
+
+  @Test
+  public void should_not_delete_shared_filter_if_not_admin() {
+    when(authorizationDao.selectGlobalPermissions("john")).thenReturn(newArrayList(UserRole.USER));
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("arthur").setShared(true));
+
+    try {
+      service.delete(1L, userSession);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not authorized to modify this filter");
+    }
+    verify(issueFilterDao, never()).delete(anyLong());
+  }
+
   @Test
   public void should_copy() {
     when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("john").setData("componentRoots=struts"));
@@ -270,6 +347,28 @@ public class IssueFilterServiceTest {
     verify(issueFilterDao).insert(any(IssueFilterDto.class));
   }
 
+  @Test
+  public void should_copy_shared_filter() {
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("arthur").setShared(true));
+    DefaultIssueFilter issueFilter = new DefaultIssueFilter().setName("Copy Of My Issue");
+
+    DefaultIssueFilter result = service.copy(1L, issueFilter, userSession);
+    assertThat(result.name()).isEqualTo("Copy Of My Issue");
+    assertThat(result.user()).isEqualTo("john");
+
+    verify(issueFilterDao).insert(any(IssueFilterDto.class));
+  }
+
+  @Test
+  public void should_add_favorite_on_copy() {
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("john").setData("componentRoots=struts"));
+    DefaultIssueFilter issueFilter = new DefaultIssueFilter().setName("Copy Of My Issue");
+    service.copy(1L, issueFilter, userSession);
+
+    verify(issueFilterDao).insert(any(IssueFilterDto.class));
+    verify(issueFilterFavouriteDao).insert(any(IssueFilterFavouriteDto.class));
+  }
+
   @Test
   public void should_execute_from_issue_query() {
     IssueQuery issueQuery = IssueQuery.builder().build();
@@ -292,6 +391,14 @@ public class IssueFilterServiceTest {
     assertThat(issueQuery.componentRoots()).contains("struts");
   }
 
+  @Test
+  public void should_find_shared_issue_filter() {
+    when(issueFilterDao.selectSharedForUser("john")).thenReturn(newArrayList(new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("john").setShared(true)));
+
+    List<DefaultIssueFilter> results = service.findSharedFilters(userSession);
+    assertThat(results).hasSize(1);
+  }
+
   @Test
   public void should_find_favourite_issue_filter() {
     when(issueFilterDao.selectByUserWithOnlyFavoriteFilters("john")).thenReturn(newArrayList(new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("john")));
@@ -323,6 +430,21 @@ public class IssueFilterServiceTest {
     assertThat(issueFilterFavouriteDto.getUserLogin()).isEqualTo("john");
   }
 
+  @Test
+  public void should_add_favourite_on_shared_filter() {
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("arthur").setShared(true));
+    // The filter is not in the favorite list --> add to favorite
+    when(issueFilterFavouriteDao.selectByUserAndIssueFilterId("john", 1L)).thenReturn(null);
+
+    ArgumentCaptor<IssueFilterFavouriteDto> issueFilterFavouriteDtoCaptor = ArgumentCaptor.forClass(IssueFilterFavouriteDto.class);
+    service.toggleFavouriteIssueFilter(1L, userSession);
+    verify(issueFilterFavouriteDao).insert(issueFilterFavouriteDtoCaptor.capture());
+
+    IssueFilterFavouriteDto issueFilterFavouriteDto = issueFilterFavouriteDtoCaptor.getValue();
+    assertThat(issueFilterFavouriteDto.getIssueFilterId()).isEqualTo(1L);
+    assertThat(issueFilterFavouriteDto.getUserLogin()).isEqualTo("john");
+  }
+
   @Test
   public void should_delete_favourite_issue_filter_id() {
     when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("john").setData("componentRoots=struts"));