*/
package org.sonar.core.rule;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.api.ServerExtension;
import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.ServerExtension;
+import org.sonar.core.persistence.MyBatis;
import java.util.List;
SqlSession session = myBatis.openSession();
try {
insert(newRuleTag, session);
+ session.commit();
} finally {
MyBatis.closeQuietly(session);
}
SqlSession session = myBatis.openSession();
try {
delete(tagId, session);
+ session.commit();
} finally {
MyBatis.closeQuietly(session);
}
public void delete(long tagId, SqlSession session) {
session.getMapper(RuleTagMapper.class).delete(tagId);
}
+
+ public Long selectId(String tag) {
+ SqlSession session = myBatis.openSession();
+ try {
+ return selectId(tag, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public Long selectId(String tag, SqlSession session) {
+ return session.getMapper(RuleTagMapper.class).selectId(tag);
+ }
}
void insert(RuleTagDto newTag);
void delete(Long tagId);
+
+ Long selectId(String tag);
}
DELETE FROM rule_tags WHERE id=#{tagId}
</update>
+ <select id="selectId" resultType="Long" parameterType="String">
+ SELECT id
+ FROM rule_tags
+ WHERE tag=#{tag}
+ </select>
+
</mapper>
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.core.rule;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class RuleTagDaoTest extends AbstractDaoTestCase {
+
+ RuleTagDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new RuleTagDao(getMyBatis());
+ }
+
+ @Test
+ public void should_select_all_tags() {
+ setupData("shared");
+
+ assertThat(dao.selectAll()).hasSize(3);
+ }
+
+ @Test
+ public void should_select_id() {
+ setupData("shared");
+
+ assertThat(dao.selectId("tag1")).isEqualTo(1L);
+ assertThat(dao.selectId("unknown")).isNull();
+ }
+
+ @Test
+ public void should_insert_tag() {
+ setupData("shared");
+
+ dao.insert(new RuleTagDto().setTag("tag4"));
+ checkTable("insert", "rule_tags");
+ }
+
+ @Test
+ public void should_delete_tag() {
+ setupData("shared");
+
+ dao.delete(1L);
+ checkTable("delete", "rule_tags");
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<dataset>
+
+ <rule_tags id="2" tag="tag2"/>
+ <rule_tags id="3" tag="tag3"/>
+
+</dataset>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<dataset>
+
+ <rule_tags id="1" tag="tag1"/>
+ <rule_tags id="2" tag="tag2"/>
+ <rule_tags id="3" tag="tag3"/>
+ <rule_tags id="4" tag="tag4"/>
+
+</dataset>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<dataset>
+
+ <rule_tags id="1" tag="tag1"/>
+ <rule_tags id="2" tag="tag2"/>
+ <rule_tags id="3" tag="tag3"/>
+
+</dataset>
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.SqlSession;
import org.sonar.api.ServerComponent;
import org.sonar.api.rule.Severity;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.qualityprofile.db.ActiveRuleDao;
import org.sonar.core.qualityprofile.db.ActiveRuleDto;
-import org.sonar.core.rule.RuleDao;
-import org.sonar.core.rule.RuleDto;
-import org.sonar.core.rule.RuleParamDto;
-import org.sonar.core.rule.RuleRuleTagDto;
+import org.sonar.core.rule.*;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.rule.RuleRegistry;
import org.sonar.server.user.UserSession;
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import static com.google.common.collect.Lists.newArrayList;
private final MyBatis myBatis;
private final ActiveRuleDao activeRuleDao;
private final RuleDao ruleDao;
+ private final RuleTagDao ruleTagDao;
private final RuleRegistry ruleRegistry;
private final System2 system;
- public QProfileRuleOperations(MyBatis myBatis, ActiveRuleDao activeRuleDao, RuleDao ruleDao, RuleRegistry ruleRegistry) {
- this(myBatis, activeRuleDao, ruleDao, ruleRegistry, System2.INSTANCE);
+ public QProfileRuleOperations(MyBatis myBatis, ActiveRuleDao activeRuleDao, RuleDao ruleDao, RuleTagDao ruleTagDao, RuleRegistry ruleRegistry) {
+ this(myBatis, activeRuleDao, ruleDao, ruleTagDao, ruleRegistry, System2.INSTANCE);
}
@VisibleForTesting
- QProfileRuleOperations(MyBatis myBatis, ActiveRuleDao activeRuleDao, RuleDao ruleDao, RuleRegistry ruleRegistry, System2 system) {
+ QProfileRuleOperations(MyBatis myBatis, ActiveRuleDao activeRuleDao, RuleDao ruleDao, RuleTagDao ruleTagDao, RuleRegistry ruleRegistry, System2 system) {
this.myBatis = myBatis;
this.activeRuleDao = activeRuleDao;
this.ruleDao = ruleDao;
+ this.ruleTagDao = ruleTagDao;
this.ruleRegistry = ruleRegistry;
this.system = system;
}
}
}
+ public void updateTags(RuleDto rule, List<String> newTags, UserSession userSession) {
+ checkPermission(userSession);
+ SqlSession session = myBatis.openSession();
+ try {
+ boolean ruleChanged = false;
+ Map<String, Long> neededTagIds = Maps.newHashMap();
+ Set<String> unknownTags = Sets.newHashSet();
+ for (String tag: newTags) {
+ Long tagId = ruleTagDao.selectId(tag, session);
+ if (tagId == null) {
+ unknownTags.add(tag);
+ } else {
+ neededTagIds.put(tag, tagId);
+ }
+ }
+ if (!unknownTags.isEmpty()) {
+ throw new NotFoundException("The following tags are unknown and must be created before association: "
+ + StringUtils.join(unknownTags, ", "));
+ }
+
+ Set<String> tagsToKeep = Sets.newHashSet();
+ final Integer ruleId = rule.getId();
+
+ List<RuleRuleTagDto> currentTags = ruleDao.selectTags(ruleId, session);
+ for (RuleRuleTagDto existingTag: currentTags) {
+ if(existingTag.getType() == RuleTagType.ADMIN && !newTags.contains(existingTag.getTag())) {
+ ruleDao.deleteTag(existingTag, session);
+ ruleChanged = true;
+ } else {
+ tagsToKeep.add(existingTag.getTag());
+ }
+ }
+
+ for (String tag: newTags) {
+ if (! tagsToKeep.contains(tag)) {
+ ruleDao.insert(new RuleRuleTagDto().setRuleId(ruleId).setTagId(neededTagIds.get(tag)).setType(RuleTagType.ADMIN), session);
+ ruleChanged = true;
+ }
+ }
+
+ if (ruleChanged) {
+ rule.setUpdatedAt(new Date(system.now()));
+ ruleDao.update(rule, session);
+ session.commit();
+ reindexRule(rule, session);
+ }
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
private void reindexRule(RuleDto rule, SqlSession session) {
reindexRule(rule, ruleDao.selectParameters(rule.getId(), session), ruleDao.selectTags(rule.getId(), session));
}
private static int getSeverityOrdinal(String severity) {
return Severity.ALL.indexOf(severity);
}
-
}
package org.sonar.server.qualityprofile;
+import com.google.common.collect.ImmutableList;
+
import com.google.common.base.Strings;
import org.sonar.api.ServerComponent;
import org.sonar.api.component.Component;
import org.sonar.server.rule.ProfileRules;
import org.sonar.server.rule.RuleTagLookup;
import org.sonar.server.user.UserSession;
+import org.sonar.server.util.RubyUtils;
import org.sonar.server.util.Validation;
import javax.annotation.CheckForNull;
return ruleTagLookup.listAllTags();
}
+ public QProfileRule updateRuleTags(int ruleId, Object tags) {
+ RuleDto rule = findRuleNotNull(ruleId);
+ List<String> newTags = RubyUtils.toStrings(tags);
+ if (newTags == null) {
+ newTags = ImmutableList.of();
+ }
+ ruleOperations.updateTags(rule, newTags, UserSession.get());
+ return rules.findByRuleId(ruleId);
+ }
+
//
// Quality profile validation
//
render :partial => 'active_rule_note', :locals => {:rule => rule}
end
+ def show_select_tags
+ rule = Internal.quality_profiles.findByRule(params[:rule_id].to_i)
+ tags = []
+ Internal.quality_profiles.listAllTags().sort.each do |tag|
+ tags.push({
+ :value => tag,
+ :selected => (rule.systemTags.contains?(tag) || rule.adminTags.contains?(tag)),
+ :read_only => rule.systemTags.contains?(tag)
+ })
+ end
+ render :partial => 'select_tags', :locals => { :rule => rule, :tags => tags, :profile_id => params[:profile_id] }
+ end
+
+ def select_tags
+ rule = Internal.quality_profiles.updateRuleTags(params[:rule_id].to_i, params[:tags])
+ render :partial => 'rule_tags', :locals => {:rule => rule}
+ end
private
<% end %>
</div>
<% end %>
- <% unless rule.systemTags.isEmpty && rule.adminTags.isEmpty %>
- <div class="rule-tags">
- <% rule.systemTags.each do |tag| %>
- <span id="rule_tag_<%= rule.id -%>_<%= tag -%>"><%= tag -%></span>
- <% end %>
- <% rule.adminTags.each do |tag| %>
- <span id="rule_tag_<%= rule.id -%>_<%= tag -%>"><%= tag -%></span>
- <% end %>
+ <div class="rule-tags" id="rule_tags_<%= rule.id %>">
+ <%= render :partial => 'rule_tags', :locals => {:rule => rule} -%>
</div>
- <% end %>
-
<div class="h3 rule-title"><%= link_to_function("#{h rule.name}", nil, :class => "") do |page|
page.toggle "desc_#{rule.id}"
</table>
<% end %>
+ <% if profiles_administrator? %>
+ <div id="open_rule_tags_<%= rule.id -%>">
+ <a class="link-action open-modal" href="<%=ApplicationController.root_context-%>/rules_configuration/show_select_tags?rule_id=<%=rule.id-%>&profile_id=<%= u profile.id %>">Select Tags</a>
+ </div>
+ <% end %>
+
<% if is_activated %>
<div id="active_rule_note_<%= rule.activeRuleId -%>">
<%= render :partial => 'active_rule_note', :locals => {:rule => rule} %>
--- /dev/null
+<% #locals = rule -%>
+<% unless rule.systemTags.isEmpty && rule.adminTags.isEmpty %>
+ <% rule.systemTags.each do |tag| %>
+ <span id="rule_tag_<%= rule.id -%>_<%= tag -%>"><%= tag -%></span>
+ <% end %>
+ <% rule.adminTags.each do |tag| %>
+ <span id="rule_tag_<%= rule.id -%>_<%= tag -%>"><%= tag -%></span>
+ <% end %>
+<% end %>
--- /dev/null
+<% #locals = rule, tags, profile_id -%>
+<form onsubmit="$j.ajax({
+ url:'<%=ApplicationController.root_context-%>/rules_configuration/select_tags',
+ success:function(response){ closeModalWindow();$j('#rule_tags_<%=rule.id-%>').html(response); },
+ error:function(response){ $j('#select-tags-error').html(response); },
+ data: $j(this).serialize(),
+ type:'post'});
+ return false;"
+ method="post"
+ action="<%=ApplicationController.root_context-%>/rules_configuration/select_tags">
+ <%= hidden_field_tag :rule_id, rule.id -%>
+ <%= hidden_field_tag :profile_id, profile_id -%>
+ <fieldset>
+ <div class="modal-head">
+ <h2>Select Tags for Rule: <%= h rule.name -%></h2><%# TODO i18n?-%>
+ </div>
+
+ <div class="modal-body">
+ <div class="modal-error" id="select-tags-error"/>
+ <div id="select-rule-tags" style="height: 200px; overflow-y: visible">
+ <ul>
+ <% tags.each do |tag| -%>
+ <li style="padding: 5px 2px">
+ <%= check_box_tag 'tags[]', tag[:value], tag[:selected], :id => 'tags_'+tag[:value], :disabled => tag[:read_only] -%>
+ <label for="tags_<%= tag[:value] -%>"><%= tag[:value] -%></label>
+ </li>
+ <% end -%>
+ </ul>
+ </div>
+ </div>
+
+ <div class="modal-foot">
+ <%= submit_tag message('Update Tags'), { :id => 'update_tags_' + rule.id.to_s } -%>
+ <a href="#" onclick="return closeModalWindow()" id="confirm-cancel"><%= h message('cancel') -%></a>
+ </div>
+ </fieldset>
+</form>
package org.sonar.server.qualityprofile;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.sonar.core.qualityprofile.db.ActiveRuleDto;
import org.sonar.core.rule.*;
import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.rule.RuleRegistry;
import org.sonar.server.user.MockUserSession;
import org.sonar.server.user.UserSession;
@Mock
RuleDao ruleDao;
+ @Mock
+ RuleTagDao ruleTagDao;
+
@Mock
RuleRegistry ruleRegistry;
}
}).when(activeRuleDao).insert(any(ActiveRuleDto.class), any(SqlSession.class));
- operations = new QProfileRuleOperations(myBatis, activeRuleDao, ruleDao, ruleRegistry, system);
+ operations = new QProfileRuleOperations(myBatis, activeRuleDao, ruleDao, ruleTagDao, ruleRegistry, system);
}
@Test
verify(ruleRegistry).deleteActiveRules(newArrayList(activeRuleId));
}
+ @Test(expected = ForbiddenException.class)
+ public void should_fail_update_tags_on_unauthorized_user() {
+ operations.updateTags(new RuleDto(), ImmutableList.of("polop"), unauthorizedUserSession);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void should_fail_update_tags_on_unknown_tag() {
+ final String tag = "polop";
+ when(ruleTagDao.selectId(tag, session)).thenReturn(null);
+ operations.updateTags(new RuleDto(), ImmutableList.of(tag), authorizedUserSession);
+ }
+
+ @Test
+ public void should_add_new_tags() {
+ final int ruleId = 24;
+ final RuleDto rule = new RuleDto().setId(ruleId);
+ final String tag = "polop";
+ final long tagId = 42L;
+ when(ruleTagDao.selectId(tag, session)).thenReturn(tagId);
+
+ operations.updateTags(rule, ImmutableList.of(tag), authorizedUserSession);
+
+ verify(ruleTagDao).selectId(tag, session);
+ ArgumentCaptor<RuleRuleTagDto> capture = ArgumentCaptor.forClass(RuleRuleTagDto.class);
+ verify(ruleDao).insert(capture.capture(), eq(session));
+ final RuleRuleTagDto newTag = capture.getValue();
+ assertThat(newTag.getRuleId()).isEqualTo(ruleId);
+ assertThat(newTag.getTagId()).isEqualTo(tagId);
+ assertThat(newTag.getType()).isEqualTo(RuleTagType.ADMIN);
+ verify(ruleDao).update(rule, session);
+ verify(session).commit();
+ }
+
+ @Test
+ public void should_delete_removed_tags() {
+ final int ruleId = 24;
+ final RuleDto rule = new RuleDto().setId(ruleId);
+ final String tag = "polop";
+ RuleRuleTagDto existingTag = new RuleRuleTagDto().setTag(tag).setType(RuleTagType.ADMIN);
+ when(ruleDao.selectTags(ruleId, session)).thenReturn(ImmutableList.of(existingTag));
+
+
+ operations.updateTags(rule, ImmutableList.<String>of(), authorizedUserSession);
+
+ verify(ruleDao, atLeast(1)).selectTags(ruleId, session);
+ verify(ruleDao).deleteTag(existingTag, session);
+ verify(ruleDao).update(rule, session);
+ verify(session).commit();
+ }
+
+ @Test
+ public void should_not_update_rule_if_tags_unchanged() {
+ final int ruleId = 24;
+ final RuleDto rule = new RuleDto().setId(ruleId);
+ final String tag = "polop";
+ final long tagId = 42L;
+ when(ruleTagDao.selectId(tag, session)).thenReturn(tagId);
+ RuleRuleTagDto existingTag = new RuleRuleTagDto().setTag(tag).setType(RuleTagType.ADMIN);
+ when(ruleDao.selectTags(ruleId, session)).thenReturn(ImmutableList.of(existingTag));
+
+ operations.updateTags(rule, ImmutableList.of(tag), authorizedUserSession);
+
+ verify(ruleTagDao).selectId(tag, session);
+ verify(ruleDao).selectTags(ruleId, session);
+ verify(ruleDao, never()).update(rule);
+ }
}
package org.sonar.server.qualityprofile;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.junit.Before;
import org.sonar.server.rule.RuleTagLookup;
import org.sonar.server.user.UserSession;
+import java.util.Collection;
+import java.util.List;
import java.util.Map;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
assertThat(qProfiles.countActiveRules(rule)).isEqualTo(2);
}
+ @Test
+ public void should_find_all_tags() {
+ Collection<String> tags = ImmutableList.of("tag1", "tag2");
+ when(ruleTagLookup.listAllTags()).thenReturn(tags);
+ assertThat(qProfiles.listAllTags()).isEqualTo(tags);
+ verify(ruleTagLookup).listAllTags();
+ }
+
+ @Test
+ public void should_pass_tags_to_update() {
+ final int ruleId = 11;
+ RuleDto rule = new RuleDto().setId(ruleId).setRepositoryKey("squid").setRuleKey("XPath_1387869254").setParentId(10);
+ when(ruleDao.selectById(ruleId)).thenReturn(rule);
+
+ qProfiles.updateRuleTags(ruleId, null);
+ verify(ruleOperations).updateTags(eq(rule), isA(List.class), any(UserSession.class));
+ }
+
+ @Test
+ public void should_prepare_empty_tag_list() {
+ final int ruleId = 11;
+ RuleDto rule = new RuleDto().setId(ruleId).setRepositoryKey("squid").setRuleKey("XPath_1387869254").setParentId(10);
+ when(ruleDao.selectById(ruleId)).thenReturn(rule);
+
+ List<String> tags = ImmutableList.of("tag1", "tag2");
+ qProfiles.updateRuleTags(ruleId, tags);
+ verify(ruleOperations).updateTags(eq(rule), eq(tags), any(UserSession.class));
+ }
}