]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5056 Create rename, move up and move down actions
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 18 Mar 2014 16:24:30 +0000 (17:24 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 18 Mar 2014 16:24:39 +0000 (17:24 +0100)
sonar-core/src/main/java/org/sonar/core/technicaldebt/db/CharacteristicDao.java
sonar-core/src/main/java/org/sonar/core/technicaldebt/db/CharacteristicMapper.java
sonar-core/src/main/resources/org/sonar/core/technicaldebt/db/CharacteristicMapper.xml
sonar-core/src/test/java/org/sonar/core/technicaldebt/db/CharacteristicDaoTest.java
sonar-core/src/test/resources/org/sonar/core/technicaldebt/db/CharacteristicDaoTest/select_next_and_previous.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelService.java
sonar-server/src/test/java/org/sonar/server/debt/DebtModelServiceTest.java

index 0699b48354cbe41b51fd8b26056ba1b5929e53c2..05555a2848683b2226d023de1a9f5312b3f59b5c 100644 (file)
@@ -91,25 +91,33 @@ public class CharacteristicDao implements BatchComponent, ServerComponent {
   @CheckForNull
   public CharacteristicDto selectByKey(String key) {
     SqlSession session = mybatis.openSession();
-    CharacteristicMapper mapper = session.getMapper(CharacteristicMapper.class);
     try {
-      return mapper.selectByKey(key);
+      return selectByKey(key, session);
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
+  @CheckForNull
+  public CharacteristicDto selectByKey(String key, SqlSession session) {
+    return session.getMapper(CharacteristicMapper.class).selectByKey(key);
+  }
+
   @CheckForNull
   public CharacteristicDto selectById(int id) {
     SqlSession session = mybatis.openSession();
-    CharacteristicMapper mapper = session.getMapper(CharacteristicMapper.class);
     try {
-      return mapper.selectById(id);
+      return selectById(id, session);
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
+  @CheckForNull
+  public CharacteristicDto selectById(int id, SqlSession session) {
+    return session.getMapper(CharacteristicMapper.class).selectById(id);
+  }
+
   @CheckForNull
   public CharacteristicDto selectByName(String name) {
     SqlSession session = mybatis.openSession();
@@ -125,6 +133,38 @@ public class CharacteristicDao implements BatchComponent, ServerComponent {
     return session.getMapper(CharacteristicMapper.class).selectByName(name);
   }
 
+  @CheckForNull
+  public CharacteristicDto selectNext(int order, SqlSession session) {
+    List<CharacteristicDto> dtos = session.getMapper(CharacteristicMapper.class).selectNext(order);
+    return dtos.isEmpty() ? null : dtos.get(0);
+  }
+
+  @CheckForNull
+  public CharacteristicDto selectNext(int order) {
+    SqlSession session = mybatis.openSession();
+    try {
+      return selectNext(order, session);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  @CheckForNull
+  public CharacteristicDto selectPrevious(int order, SqlSession session) {
+    List<CharacteristicDto> dtos = session.getMapper(CharacteristicMapper.class).selectPrevious(order);
+    return dtos.isEmpty() ? null : dtos.get(0);
+  }
+
+  @CheckForNull
+  public CharacteristicDto selectPrevious(int order) {
+    SqlSession session = mybatis.openSession();
+    try {
+      return selectPrevious(order, session);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
   public int selectMaxCharacteristicOrder() {
     SqlSession session = mybatis.openSession();
     try {
index b63e89ad456f70c39912a3bec0d74704b0b7aef8..fe995a8c078c9e9c6db840cfada7fd09429f42c9 100644 (file)
@@ -36,6 +36,10 @@ public interface CharacteristicMapper {
 
   CharacteristicDto selectByName(String name);
 
+  List<CharacteristicDto> selectNext(int order);
+
+  List<CharacteristicDto> selectPrevious(int order);
+
   Integer selectMaxCharacteristicOrder();
 
   void insert(CharacteristicDto characteristic);
index d598985763ccdb92286396c43a097008e150dc0f..e4cde1a0ac819a7a5d7e85045e79738fa7fcc473 100644 (file)
     </where>
   </select>
 
+  <select id="selectNext" parameterType="Integer" resultType="Characteristic">
+    select <include refid="characteristicColumns"/>
+    from characteristics c
+    <where>
+      and c.characteristic_order&gt;#{order}
+      and c.parent_id is null
+      and c.enabled=${_true}
+    </where>
+    order by characteristic_order asc
+  </select>
+
+  <select id="selectPrevious" parameterType="Integer" resultType="Characteristic">
+    select <include refid="characteristicColumns"/>
+    from characteristics c
+    <where>
+      and c.characteristic_order&lt;#{order}
+      and c.parent_id is null
+      and c.enabled=${_true}
+    </where>
+    order by characteristic_order asc
+  </select>
+
   <select id="selectMaxCharacteristicOrder" resultType="Integer">
     select max(c.characteristic_order)
     from characteristics c
index 82f6dba1d01ebfcf16d78e98bea1a6b97effecce..d89af50367a914c99aff88e1b43c9cce305dcd9d 100644 (file)
@@ -138,6 +138,17 @@ public class CharacteristicDaoTest extends AbstractDaoTestCase {
     assertThat(dao.selectById(10)).isNull();
   }
 
+  @Test
+  public void select_next_and_previous_characteristic() {
+    setupData("select_next_and_previous");
+
+    assertThat(dao.selectNext(1)).isNotNull();
+    assertThat(dao.selectNext(2)).isNull();
+
+    assertThat(dao.selectPrevious(1)).isNull();
+    assertThat(dao.selectPrevious(2)).isNotNull();
+  }
+
   @Test
   public void select_max_characteristic_order() {
     setupData("shared");
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/db/CharacteristicDaoTest/select_next_and_previous.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/db/CharacteristicDaoTest/select_next_and_previous.xml
new file mode 100644 (file)
index 0000000..8f96903
--- /dev/null
@@ -0,0 +1,13 @@
+<dataset>
+
+  <!-- Root characteristic -->
+  <characteristics id="1" kee="PORTABILITY" name="Portability" parent_id="[null]" characteristic_order="1"
+                   enabled="[true]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+  <!-- Disabled root characteristic -->
+  <characteristics id="4" kee="MAINTAINABILITY" name="Maintainability" parent_id="[null]" characteristic_order="2"
+                   enabled="[true]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
index 4d314d96d5a40d541864a64ac1298eb47918eb82..367f3bd1052c692c49d737477576395c23008d18 100644 (file)
@@ -26,11 +26,13 @@ import org.apache.ibatis.session.SqlSession;
 import org.sonar.api.server.debt.DebtCharacteristic;
 import org.sonar.api.server.debt.DebtModel;
 import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
+import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.technicaldebt.db.CharacteristicDao;
 import org.sonar.core.technicaldebt.db.CharacteristicDto;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
 import org.sonar.server.util.Validation;
 
 import javax.annotation.CheckForNull;
@@ -69,7 +71,9 @@ public class DebtModelService implements DebtModel {
     return dto != null ? toCharacteristic(dto) : null;
   }
 
-  public DebtCharacteristic createCharacteristic(String name, @Nullable Integer parentId) {
+  public DebtCharacteristic create(String name, @Nullable Integer parentId) {
+    checkPermission();
+
     SqlSession session = mybatis.openSession();
     try {
       checkNotAlreadyExists(name, session);
@@ -81,14 +85,14 @@ public class DebtModelService implements DebtModel {
 
       // New sub characteristic
       if (parentId != null) {
-        CharacteristicDto parent = findCharacteristic(parentId);
+        CharacteristicDto parent = findCharacteristic(parentId, session);
         if (parent.getParentId() != null) {
           throw new BadRequestException("A sub characteristic can not have a sub characteristic as parent.");
         }
         newCharacteristic.setParentId(parent.getId());
       } else {
         // New root characteristic
-        newCharacteristic.setOrder(dao.selectMaxCharacteristicOrder(session)+1);
+        newCharacteristic.setOrder(dao.selectMaxCharacteristicOrder(session) + 1);
       }
       dao.insert(newCharacteristic, session);
       session.commit();
@@ -98,8 +102,62 @@ public class DebtModelService implements DebtModel {
     }
   }
 
-  private CharacteristicDto findCharacteristic(Integer id){
-    CharacteristicDto dto = dao.selectById(id);
+  public DebtCharacteristic rename(int characteristicId, String newName) {
+    checkPermission();
+
+    SqlSession session = mybatis.openSession();
+    try {
+      checkNotAlreadyExists(newName, session);
+
+      CharacteristicDto dto = findCharacteristic(characteristicId, session);
+      if (!dto.getName().equals(newName)) {
+        dto.setName(newName);
+        dao.update(dto, session);
+        session.commit();
+      }
+      return toCharacteristic(dto);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public DebtCharacteristic moveUp(int characteristicId) {
+    return move(characteristicId, true);
+  }
+
+  public DebtCharacteristic moveDown(int characteristicId) {
+    return move(characteristicId, false);
+  }
+
+  private DebtCharacteristic move(int characteristicId, boolean moveUpOrDown) {
+    checkPermission();
+
+    SqlSession session = mybatis.openSession();
+    try {
+      CharacteristicDto dto = findCharacteristic(characteristicId, session);
+      int currentOrder = dto.getOrder();
+      CharacteristicDto dtoToSwitchOrderWith = moveUpOrDown ? dao.selectPrevious(currentOrder, session) : dao.selectNext(currentOrder, session);
+
+      // Do nothing when characteristic is already to the new location
+      if (dtoToSwitchOrderWith == null) {
+        return toCharacteristic(dto);
+      }
+      int nextOrder = dtoToSwitchOrderWith.getOrder();
+      dtoToSwitchOrderWith.setOrder(currentOrder);
+      dao.update(dtoToSwitchOrderWith, session);
+
+      dto.setOrder(nextOrder);
+      dao.update(dto, session);
+
+      session.commit();
+      return toCharacteristic(dto);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  private CharacteristicDto findCharacteristic(Integer id, SqlSession session) {
+    CharacteristicDto dto = dao.selectById(id, session);
     if (dto == null) {
       throw new NotFoundException(String.format("Characteristic with id %s does not exists.", id));
     }
@@ -112,6 +170,10 @@ public class DebtModelService implements DebtModel {
     }
   }
 
+  private void checkPermission() {
+    UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+  }
+
   private static List<DebtCharacteristic> toCharacteristics(Collection<CharacteristicDto> dtos) {
     return newArrayList(Iterables.transform(dtos, new Function<CharacteristicDto, DebtCharacteristic>() {
       @Override
index 3277e46cf901f929c0f43332f30f7124d38ddac7..61a58305daf50b0b61099f833f7a1941221aa474 100644 (file)
@@ -23,23 +23,26 @@ import org.apache.ibatis.session.SqlSession;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.technicaldebt.db.CharacteristicDao;
 import org.sonar.core.technicaldebt.db.CharacteristicDto;
 import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.MockUserSession;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
 import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 @RunWith(MockitoJUnitRunner.class)
 public class DebtModelServiceTest {
@@ -53,13 +56,11 @@ public class DebtModelServiceTest {
   @Mock
   SqlSession session;
 
-  DebtModelService service;
-
   CharacteristicDto rootCharacteristicDto = new CharacteristicDto()
     .setId(1)
     .setKey("MEMORY_EFFICIENCY")
     .setName("Memory use")
-    .setOrder(1);
+    .setOrder(2);
 
   CharacteristicDto characteristicDto = new CharacteristicDto()
     .setId(2)
@@ -69,10 +70,13 @@ public class DebtModelServiceTest {
 
   int currentId;
 
+  DebtModelService service;
+
   @Before
   public void setUp() throws Exception {
-    currentId = 10;
+    MockUserSession.set().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
 
+    currentId = 10;
     // Associate an id when inserting an object to simulate the db id generator
     doAnswer(new Answer() {
       public Object answer(InvocationOnMock invocation) {
@@ -107,7 +111,7 @@ public class DebtModelServiceTest {
     assertThat(characteristic.id()).isEqualTo(1);
     assertThat(characteristic.key()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(characteristic.name()).isEqualTo("Memory use");
-    assertThat(characteristic.order()).isEqualTo(1);
+    assertThat(characteristic.order()).isEqualTo(2);
     assertThat(characteristic.parentId()).isNull();
 
     assertThat(service.characteristicById(111)).isNull();
@@ -115,9 +119,9 @@ public class DebtModelServiceTest {
 
   @Test
   public void create_sub_characteristic() {
-    when(dao.selectById(1)).thenReturn(rootCharacteristicDto);
+    when(dao.selectById(1, session)).thenReturn(rootCharacteristicDto);
 
-    DebtCharacteristic result = service.createCharacteristic("Compilation name", 1);
+    DebtCharacteristic result = service.create("Compilation name", 1);
 
     assertThat(result.id()).isEqualTo(currentId);
     assertThat(result.key()).isEqualTo("COMPILATION_NAME");
@@ -127,10 +131,10 @@ public class DebtModelServiceTest {
 
   @Test
   public void fail_to_create_sub_characteristic_when_parent_id_is_not_a_root_characteristic() {
-    when(dao.selectById(1)).thenReturn(characteristicDto);
+    when(dao.selectById(1, session)).thenReturn(characteristicDto);
 
     try {
-      service.createCharacteristic("Compilation", 1);
+      service.create("Compilation", 1);
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("A sub characteristic can not have a sub characteristic as parent.");
@@ -139,10 +143,10 @@ public class DebtModelServiceTest {
 
   @Test
   public void fail_to_create_sub_characteristic_when_parent_does_not_exists() {
-    when(dao.selectById(1)).thenReturn(null);
+    when(dao.selectById(1, session)).thenReturn(null);
 
     try {
-      service.createCharacteristic("Compilation", 1);
+      service.create("Compilation", 1);
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Characteristic with id 1 does not exists.");
@@ -152,10 +156,10 @@ public class DebtModelServiceTest {
   @Test
   public void fail_to_create_sub_characteristic_when_name_already_used() {
     when(dao.selectByName("Compilation", session)).thenReturn(new CharacteristicDto());
-    when(dao.selectById(1)).thenReturn(rootCharacteristicDto);
+    when(dao.selectById(1, session)).thenReturn(rootCharacteristicDto);
 
     try {
-      service.createCharacteristic("Compilation", 1);
+      service.create("Compilation", 1);
       fail();
     } catch (BadRequestException e) {
       assertThat(e.l10nKey()).isEqualTo("errors.is_already_used");
@@ -163,16 +167,123 @@ public class DebtModelServiceTest {
     }
   }
 
+  @Test
+  public void fail_to_create_sub_characteristic_when_wrong_permission() {
+    MockUserSession.set().setGlobalPermissions(GlobalPermissions.DASHBOARD_SHARING);
+
+    try {
+      service.create("Compilation", 1);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(ForbiddenException.class);
+    }
+  }
+
   @Test
   public void create_characteristic() {
-    when(dao.selectMaxCharacteristicOrder(session)).thenReturn(1);
+    when(dao.selectMaxCharacteristicOrder(session)).thenReturn(2);
 
-    DebtCharacteristic result = service.createCharacteristic("Portability", null);
+    DebtCharacteristic result = service.create("Portability", null);
 
     assertThat(result.id()).isEqualTo(currentId);
     assertThat(result.key()).isEqualTo("PORTABILITY");
     assertThat(result.name()).isEqualTo("Portability");
-    assertThat(result.order()).isEqualTo(2);
+    assertThat(result.order()).isEqualTo(3);
+  }
+
+  @Test
+  public void create_first_characteristic() {
+    when(dao.selectMaxCharacteristicOrder(session)).thenReturn(0);
+
+    DebtCharacteristic result = service.create("Portability", null);
+
+    assertThat(result.id()).isEqualTo(currentId);
+    assertThat(result.key()).isEqualTo("PORTABILITY");
+    assertThat(result.name()).isEqualTo("Portability");
+    assertThat(result.order()).isEqualTo(1);
+  }
+
+  @Test
+  public void rename_characteristic() {
+    when(dao.selectById(10, session)).thenReturn(characteristicDto);
+
+    DebtCharacteristic result = service.rename(10, "New Efficiency");
+
+    assertThat(result.key()).isEqualTo("EFFICIENCY");
+    assertThat(result.name()).isEqualTo("New Efficiency");
+  }
+
+  @Test
+  public void not_rename_characteristic_when_renaming_with_same_name() {
+    when(dao.selectById(10, session)).thenReturn(characteristicDto);
+
+    service.rename(10, "Efficiency");
+
+    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
+  }
+
+  @Test
+  public void fail_to_rename_unknown_characteristic() {
+    when(dao.selectById(10, session)).thenReturn(null);
+
+    try {
+      service.rename(10, "New Efficiency");
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Characteristic with id 10 does not exists.");
+    }
+  }
+
+  @Test
+  public void move_up() {
+    when(dao.selectById(10, session)).thenReturn(new CharacteristicDto().setId(10).setOrder(2));
+    when(dao.selectPrevious(2, session)).thenReturn(new CharacteristicDto().setId(2).setOrder(1));
+
+    DebtCharacteristic result = service.moveUp(10);
+
+    ArgumentCaptor<CharacteristicDto> argument = ArgumentCaptor.forClass(CharacteristicDto.class);
+    verify(dao, times(2)).update(argument.capture(), eq(session));
+
+    assertThat(result.order()).isEqualTo(1);
+    assertThat(argument.getAllValues().get(0).getOrder()).isEqualTo(2);
+    assertThat(argument.getAllValues().get(1).getOrder()).isEqualTo(1);
+  }
+
+  @Test
+  public void do_nothing_when_move_up_and_already_on_top() {
+    CharacteristicDto dto = new CharacteristicDto().setId(10).setOrder(1);
+    when(dao.selectById(10, session)).thenReturn(dto);
+    when(dao.selectPrevious(1, session)).thenReturn(null);
+
+    service.moveUp(10);
+
+    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
+  }
+
+  @Test
+  public void move_down() {
+    when(dao.selectById(10, session)).thenReturn(new CharacteristicDto().setId(10).setOrder(2));
+    when(dao.selectNext(2, session)).thenReturn(new CharacteristicDto().setId(2).setOrder(3));
+
+    DebtCharacteristic result = service.moveDown(10);
+
+    ArgumentCaptor<CharacteristicDto> argument = ArgumentCaptor.forClass(CharacteristicDto.class);
+    verify(dao, times(2)).update(argument.capture(), eq(session));
+
+    assertThat(result.order()).isEqualTo(3);
+    assertThat(argument.getAllValues().get(0).getOrder()).isEqualTo(2);
+    assertThat(argument.getAllValues().get(1).getOrder()).isEqualTo(3);
+  }
+
+  @Test
+  public void do_nothing_when_move_down_and_already_on_bottom() {
+    CharacteristicDto dto = new CharacteristicDto().setId(10).setOrder(5);
+    when(dao.selectById(10, session)).thenReturn(dto);
+    when(dao.selectNext(5, session)).thenReturn(null);
+
+    service.moveDown(10);
+
+    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
   }
 
 }