aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporter.java33
-rw-r--r--plugins/sonar-checkstyle-plugin/src/main/resources/org/sonar/plugins/checkstyle/profile-sun-conventions.xml10
-rw-r--r--plugins/sonar-checkstyle-plugin/src/test/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest.java13
-rw-r--r--plugins/sonar-checkstyle-plugin/src/test/resources/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest/importingFiltersIsNotSupported.xml9
-rw-r--r--sonar-core/src/main/java/org/sonar/core/rule/DefaultRuleProvider.java18
-rw-r--r--sonar-core/src/test/java/org/sonar/core/rule/DefaultRuleProviderTest.java8
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/profiles/ProfilePrototype.java9
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/utils/ValidationMessages.java23
-rw-r--r--sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java22
-rw-r--r--sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java104
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java27
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/alerts_controller.rb94
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb237
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/rules_configuration_controller.rb33
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/alerts_helper.rb18
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb8
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_edit.html.erb18
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_new.html.erb18
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_show.html.erb4
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/alerts/index.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/profiles/_tabs.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb104
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/profiles/projects.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/index.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/config/routes.rb3
25 files changed, 520 insertions, 301 deletions
diff --git a/plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporter.java b/plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporter.java
index c9561aea69e..e397e5bbdeb 100644
--- a/plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporter.java
+++ b/plugins/sonar-checkstyle-plugin/src/main/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporter.java
@@ -64,7 +64,7 @@ public class CheckstyleProfileImporter extends ProfileImporter {
}
}
} catch (XMLStreamException e) {
- messages.addError("unvalidXml", "XML is not valid: " + e.getMessage());
+ messages.addError("checkstyle.import.unvalidXml", "XML is not valid: " + e.getMessage());
}
return profile;
}
@@ -80,23 +80,42 @@ public class CheckstyleProfileImporter extends ProfileImporter {
}
private void processModule(ProfilePrototype profile, String path, SMInputCursor moduleCursor, ValidationMessages messages) throws XMLStreamException {
- String configKey = path + moduleCursor.getAttrValue("name");
- ProfilePrototype.RulePrototype rule = ProfilePrototype.RulePrototype.createByConfigKey(CheckstyleConstants.REPOSITORY_KEY, configKey);
+ String configKey = moduleCursor.getAttrValue("name");
+ if (isFilter(configKey)) {
+ messages.addWarning("checkstyle.import.filtersNotSupported", "Checkstyle filters are not imported: " + configKey);
+ } else if (isIgnored(configKey)) {
+
+ } else {
+ ProfilePrototype.RulePrototype rule = ProfilePrototype.RulePrototype.createByConfigKey(CheckstyleConstants.REPOSITORY_KEY, path + configKey);
+ processProperties(moduleCursor, messages, rule);
+ profile.activateRule(rule);
+ }
+ }
+
+ static boolean isIgnored(String configKey) {
+ return StringUtils.equals(configKey, "FileContentsHolder");
+ }
+
+ static boolean isFilter(String configKey) {
+ return StringUtils.equals(configKey, "SuppressionCommentFilter") ||
+ StringUtils.equals(configKey, "SeverityMatchFilter") ||
+ StringUtils.equals(configKey, "SuppressionFilter") ||
+ StringUtils.equals(configKey, "SuppressWithNearbyCommentFilter");
+ }
+
+ private void processProperties(SMInputCursor moduleCursor, ValidationMessages messages, ProfilePrototype.RulePrototype rule) throws XMLStreamException {
SMInputCursor propertyCursor = moduleCursor.childElementCursor("property");
while (propertyCursor.getNext() != null) {
processProperty(rule, propertyCursor, messages);
}
-
- profile.activateRule(rule);
-
}
private void processProperty(ProfilePrototype.RulePrototype rule, SMInputCursor propertyCursor, ValidationMessages messages) throws XMLStreamException {
String key = propertyCursor.getAttrValue("name");
String value = propertyCursor.getAttrValue("value");
if (StringUtils.equals("id", key)) {
- messages.addWarning("checkstyle.idPropertyNotSupported", "The property 'id' is not supported.");
+ messages.addWarning("checkstyle.import.idPropertyNotSupported", "The checkstyle property 'id' is not supported in the rule: " + rule.getConfigKey());
} else if (StringUtils.equals("severity", key)) {
rule.setPriority(CheckstyleSeverityUtils.fromSeverity(value));
diff --git a/plugins/sonar-checkstyle-plugin/src/main/resources/org/sonar/plugins/checkstyle/profile-sun-conventions.xml b/plugins/sonar-checkstyle-plugin/src/main/resources/org/sonar/plugins/checkstyle/profile-sun-conventions.xml
index bbeff27d91c..d6fe70f3ff5 100644
--- a/plugins/sonar-checkstyle-plugin/src/main/resources/org/sonar/plugins/checkstyle/profile-sun-conventions.xml
+++ b/plugins/sonar-checkstyle-plugin/src/main/resources/org/sonar/plugins/checkstyle/profile-sun-conventions.xml
@@ -12,16 +12,6 @@
</rule>
<rule>
<repositoryKey>checkstyle</repositoryKey>
- <key>com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceAfterCheck</key>
- <parameters>
- <parameter>
- <key>tokens</key>
- <value>COMMA,SEMI,TYPECAST</value>
- </parameter>
- </parameters>
- </rule>
- <rule>
- <repositoryKey>checkstyle</repositoryKey>
<key>com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAfterCheck</key>
<parameters>
<parameter>
diff --git a/plugins/sonar-checkstyle-plugin/src/test/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest.java b/plugins/sonar-checkstyle-plugin/src/test/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest.java
index 63d9c2d40d9..1e53bba97ab 100644
--- a/plugins/sonar-checkstyle-plugin/src/test/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest.java
+++ b/plugins/sonar-checkstyle-plugin/src/test/java/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest.java
@@ -32,6 +32,7 @@ import java.io.StringReader;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
public class CheckstyleProfileImporterTest {
@@ -102,4 +103,16 @@ public class CheckstyleProfileImporterTest {
importer.importProfile(reader, messages);
assertThat(messages.getErrors().size(), is(1));
}
+
+ @Test
+ public void importingFiltersIsNotSupported() {
+ Reader reader = new StringReader(TestUtils.getResourceContent("/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest/importingFiltersIsNotSupported.xml"));
+ ProfilePrototype profile = importer.importProfile(reader, messages);
+
+ assertNull(profile.getRuleByConfigKey("checkstyle", "Checker/SuppressionCommentFilter"));
+ assertNull(profile.getRuleByConfigKey("checkstyle", "Checker/TreeWalker/FileContentsHolder"));
+ assertThat(profile.getRules().size(), is(2));
+ assertThat(messages.getWarnings().size(), is(1)); // no warning for FileContentsHolder
+ }
+
}
diff --git a/plugins/sonar-checkstyle-plugin/src/test/resources/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest/importingFiltersIsNotSupported.xml b/plugins/sonar-checkstyle-plugin/src/test/resources/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest/importingFiltersIsNotSupported.xml
new file mode 100644
index 00000000000..806ec61fae0
--- /dev/null
+++ b/plugins/sonar-checkstyle-plugin/src/test/resources/org/sonar/plugins/checkstyle/CheckstyleProfileImporterTest/importingFiltersIsNotSupported.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module name="Checker">
+ <module name="SuppressionCommentFilter"/>
+ <module name="NewlineAtEndOfFile"/>
+ <module name="TreeWalker">
+ <module name="FileContentsHolder"/>
+ <module name="InterfaceIsType"/>
+ </module>
+</module> \ No newline at end of file
diff --git a/sonar-core/src/main/java/org/sonar/core/rule/DefaultRuleProvider.java b/sonar-core/src/main/java/org/sonar/core/rule/DefaultRuleProvider.java
index faac5971355..0512cff60e9 100644
--- a/sonar-core/src/main/java/org/sonar/core/rule/DefaultRuleProvider.java
+++ b/sonar-core/src/main/java/org/sonar/core/rule/DefaultRuleProvider.java
@@ -20,6 +20,7 @@
package org.sonar.core.rule;
import org.apache.commons.lang.StringUtils;
+import org.sonar.api.database.DatabaseSession;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleProvider;
import org.sonar.api.rules.RuleQuery;
@@ -43,30 +44,33 @@ public class DefaultRuleProvider implements RuleProvider {
}
public Rule find(RuleQuery query) {
- return (Rule) createHqlQuery(query).getSingleResult();
+ DatabaseSession session = sessionFactory.getSession();
+ return (Rule)session.getSingleResult(createHqlQuery(session, query), null);
+
}
public Collection<Rule> findAll(RuleQuery query) {
- return createHqlQuery(query).getResultList();
+ DatabaseSession session = sessionFactory.getSession();
+ return createHqlQuery(session, query).getResultList();
}
- private Query createHqlQuery(RuleQuery query) {
+ private Query createHqlQuery(DatabaseSession session, RuleQuery query) {
StringBuilder hql = new StringBuilder().append("from ").append(Rule.class.getSimpleName()).append(" where enabled=true ");
Map<String,Object> params = new HashMap<String,Object>();
if (StringUtils.isNotBlank(query.getRepositoryKey())) {
- hql.append("AND pluginName=:repositoryKey");
+ hql.append("AND pluginName=:repositoryKey ");
params.put("repositoryKey", query.getRepositoryKey());
}
if (StringUtils.isNotBlank(query.getKey())) {
- hql.append("AND key=:key");
+ hql.append("AND key=:key ");
params.put("key", query.getKey());
}
if (StringUtils.isNotBlank(query.getConfigKey())) {
- hql.append("AND configKey=:configKey");
+ hql.append("AND configKey=:configKey ");
params.put("configKey", query.getConfigKey());
}
- Query hqlQuery = sessionFactory.getSession().createQuery(hql.toString());
+ Query hqlQuery = session.createQuery(hql.toString());
for (Map.Entry<String, Object> entry : params.entrySet()) {
hqlQuery.setParameter(entry.getKey(), entry.getValue());
}
diff --git a/sonar-core/src/test/java/org/sonar/core/rule/DefaultRuleProviderTest.java b/sonar-core/src/test/java/org/sonar/core/rule/DefaultRuleProviderTest.java
index 10830c4256f..69287804e8c 100644
--- a/sonar-core/src/test/java/org/sonar/core/rule/DefaultRuleProviderTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/rule/DefaultRuleProviderTest.java
@@ -45,6 +45,14 @@ public class DefaultRuleProviderTest extends AbstractDbUnitTestCase {
}
@Test
+ public void findReturnsNullIfNoResults() {
+ setupData("shared");
+ DefaultRuleProvider provider = new DefaultRuleProvider(getSessionFactory());
+ assertNull(provider.findByKey("checkstyle", "unknown"));
+ assertNull(provider.find(RuleQuery.create().withRepositoryKey("checkstyle").withConfigKey("unknown")));
+ }
+
+ @Test
public void findRepositoryRules() {
setupData("shared");
DefaultRuleProvider provider = new DefaultRuleProvider(getSessionFactory());
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/ProfilePrototype.java b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/ProfilePrototype.java
index c4cc93cef93..979981a85b8 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/ProfilePrototype.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/ProfilePrototype.java
@@ -165,7 +165,14 @@ public final class ProfilePrototype {
@Override
public String toString() {
- return new StringBuilder().append("[repository=").append(repositoryKey).append(",key=").append(key).append("]").toString();
+ StringBuilder sb = new StringBuilder().append("[repository=").append(repositoryKey);
+ if (StringUtils.isNotBlank(key)) {
+ sb.append(",key=").append(key);
+ }
+ if (StringUtils.isNotBlank(configKey)) {
+ sb.append(",configKey=").append(configKey);
+ }
+ return sb.append("]").toString();
}
}
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/ValidationMessages.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/ValidationMessages.java
index bff9074bd12..4813f447fd3 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/ValidationMessages.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/ValidationMessages.java
@@ -38,21 +38,22 @@ public final class ValidationMessages {
public boolean hasErrors() {
return !errors.isEmpty();
}
+
public List<Message> getErrors() {
return errors;
}
+ public ValidationMessages addError(String key, String label) {
+ errors.add(new Message(key, label));
+ return this;
+ }
+
public List<Message> getWarnings() {
return warnings;
}
- public List<Message> getInfos() {
- return infos;
- }
-
- public ValidationMessages addError(String key, String label) {
- errors.add(new Message(key, label));
- return this;
+ public boolean hasWarnings() {
+ return !warnings.isEmpty();
}
public ValidationMessages addWarning(String key, String label) {
@@ -60,6 +61,14 @@ public final class ValidationMessages {
return this;
}
+ public List<Message> getInfos() {
+ return infos;
+ }
+
+ public boolean hasInfos() {
+ return !infos.isEmpty();
+ }
+
public ValidationMessages addInfo(String key, String label) {
infos.add(new Message(key, label));
return this;
diff --git a/sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java b/sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java
index 2041e4c03bd..d5143a5ebf1 100644
--- a/sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java
+++ b/sonar-server/src/main/java/org/sonar/server/configuration/ProfilesManager.java
@@ -19,16 +19,13 @@
*/
package org.sonar.server.configuration;
-import org.sonar.api.Plugin;
import org.sonar.api.Plugins;
import org.sonar.api.database.DatabaseSession;
-import org.sonar.jpa.dao.RulesDao;
-import org.sonar.jpa.dao.BaseDao;
import org.sonar.api.database.model.ResourceModel;
import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.rules.*;
-
-import java.util.List;
+import org.sonar.api.rules.DefaultRulesManager;
+import org.sonar.jpa.dao.BaseDao;
+import org.sonar.jpa.dao.RulesDao;
public class ProfilesManager extends BaseDao {
@@ -54,19 +51,6 @@ public class ProfilesManager extends BaseDao {
getSession().commit();
}
-
- public void importProfile(String pluginKey, int profileId, String configuration) {
- for (RulesRepository rulesRepository : rulesManager.getRulesRepositories()) {
- Plugin plugin = plugins.getPluginByExtension(rulesRepository);
- if (rulesRepository instanceof ConfigurationImportable && pluginKey.equals(plugin.getKey())) {
- List<ActiveRule> activeRules = ((ConfigurationImportable) rulesRepository).importConfiguration(configuration, rulesDao.getRulesByPlugin(pluginKey));
- rulesDao.addActiveRulesToProfile(activeRules, profileId, pluginKey);
- getSession().commit();
- break;
- }
- }
- }
-
public void deleteProfile(int profileId) {
RulesProfile profile = getSession().getEntity(RulesProfile.class, profileId);
if (profile != null && !profile.getProvided()) {
diff --git a/sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java b/sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java
index 7e92a8078db..02dc2b0bf2d 100644
--- a/sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java
+++ b/sonar-server/src/main/java/org/sonar/server/rules/ProfilesConsole.java
@@ -23,27 +23,36 @@ import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.ServerComponent;
import org.sonar.api.database.DatabaseSession;
-import org.sonar.api.profiles.ProfileExporter;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.profiles.XMLProfileExporter;
+import org.sonar.api.profiles.*;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleProvider;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.ValidationMessages;
import org.sonar.jpa.session.DatabaseSessionFactory;
+import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
public final class ProfilesConsole implements ServerComponent {
private DatabaseSessionFactory sessionFactory;
+ private RuleProvider ruleProvider;
private List<ProfileExporter> profileExporters = new ArrayList<ProfileExporter>();
+ private List<ProfileImporter> profileImporters = new ArrayList<ProfileImporter>();
-
- public ProfilesConsole(DatabaseSessionFactory sessionFactory,
- ProfileExporter[] exporters, DeprecatedProfileExporters deprecatedExporters) {
+ public ProfilesConsole(DatabaseSessionFactory sessionFactory, RuleProvider ruleProvider,
+ ProfileExporter[] exporters, DeprecatedProfileExporters deprecatedExporters,
+ ProfileImporter[] importers) {
+ this.ruleProvider = ruleProvider;
this.sessionFactory = sessionFactory;
initProfileExporters(exporters, deprecatedExporters);
+ this.profileImporters.addAll(Arrays.asList(importers));
}
private void initProfileExporters(ProfileExporter[] exporters, DeprecatedProfileExporters deprecatedExporters) {
@@ -54,7 +63,8 @@ public final class ProfilesConsole implements ServerComponent {
}
public String backupProfile(int profileId) {
- RulesProfile profile = loadProfile(profileId);
+ DatabaseSession session = sessionFactory.getSession();
+ RulesProfile profile = loadProfile(session, profileId);
if (profile != null) {
Writer writer = new StringWriter();
XMLProfileExporter.create().exportProfile(profile, writer);
@@ -63,10 +73,48 @@ public final class ProfilesConsole implements ServerComponent {
return null;
}
- private RulesProfile loadProfile(int profileId) {
+ public ValidationMessages restoreProfile(String profileName, String language, String xmlBackup) {
DatabaseSession session = sessionFactory.getSession();
- RulesProfile profile = session.getSingleResult(RulesProfile.class, "id", profileId);
- return profile;
+ RulesProfile profile = session.getSingleResult(RulesProfile.class, "name", profileName, "language", language);
+ if (profile != null) {
+ session.remove(session);
+ }
+ profile = RulesProfile.create(profileName, language);
+ ValidationMessages messages = ValidationMessages.create();
+ ProfilePrototype prototype = XMLProfileImporter.create().importProfile(new StringReader(xmlBackup), messages);
+ completeProfileWithPrototype(profile, prototype, messages);
+ session.saveWithoutFlush(profile);
+ session.commit();
+ return messages;
+ }
+
+ private void completeProfileWithPrototype(RulesProfile profile, ProfilePrototype prototype, ValidationMessages messages) {
+ for (ProfilePrototype.RulePrototype rulePrototype : prototype.getRules()) {
+ Rule rule = findRule(rulePrototype);
+ if (rule == null) {
+ messages.addWarning("profiles.missingRule", "The following rule has been ignored: " + rulePrototype);
+
+ } else {
+ ActiveRule activeRule = profile.activateRule(rule, rulePrototype.getPriority());
+ for (Map.Entry<String, String> entry : rulePrototype.getParameters().entrySet()) {
+ activeRule.setParameter(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ private Rule findRule(ProfilePrototype.RulePrototype rulePrototype) {
+ if (StringUtils.isNotBlank(rulePrototype.getKey())) {
+ return ruleProvider.findByKey(rulePrototype.getRepositoryKey(), rulePrototype.getKey());
+ }
+ if (StringUtils.isNotBlank(rulePrototype.getConfigKey())) {
+ return ruleProvider.find(RuleQuery.create().withRepositoryKey(rulePrototype.getRepositoryKey()).withConfigKey(rulePrototype.getConfigKey()));
+ }
+ return null;
+ }
+
+ private RulesProfile loadProfile(DatabaseSession session, int profileId) {
+ return session.getSingleResult(RulesProfile.class, "id", profileId);
}
public List<ProfileExporter> getProfileExportersForLanguage(String language) {
@@ -79,8 +127,19 @@ public final class ProfilesConsole implements ServerComponent {
return result;
}
+ public List<ProfileImporter> getProfileImportersForLanguage(String language) {
+ List<ProfileImporter> result = new ArrayList<ProfileImporter>();
+ for (ProfileImporter importer : profileImporters) {
+ if (importer.getSupportedLanguages() == null || importer.getSupportedLanguages().length == 0 || ArrayUtils.contains(importer.getSupportedLanguages(), language)) {
+ result.add(importer);
+ }
+ }
+ return result;
+ }
+
public String exportProfile(int profileId, String exporterKey) {
- RulesProfile profile = loadProfile(profileId);
+ DatabaseSession session = sessionFactory.getSession();
+ RulesProfile profile = loadProfile(session, profileId);
if (profile != null) {
ProfileExporter exporter = getProfileExporter(exporterKey);
Writer writer = new StringWriter();
@@ -90,6 +149,20 @@ public final class ProfilesConsole implements ServerComponent {
return null;
}
+ public ValidationMessages importProfile(int profileId, String importerKey, String profileDefinition) {
+ ValidationMessages messages = ValidationMessages.create();
+ ProfileImporter importer = getProfileImporter(importerKey);
+ ProfilePrototype prototype = importer.importProfile(new StringReader(profileDefinition), messages);
+ if (!messages.hasErrors()) {
+ DatabaseSession session = sessionFactory.getSession();
+ RulesProfile profile = loadProfile(session, profileId); // the profile has been create in the ruby controller
+ completeProfileWithPrototype(profile, prototype, messages);
+ session.saveWithoutFlush(profile);
+ session.commit();
+ }
+ return messages;
+ }
+
public ProfileExporter getProfileExporter(String exporterKey) {
for (ProfileExporter exporter : profileExporters) {
if (StringUtils.equals(exporterKey, exporter.getKey())) {
@@ -98,4 +171,13 @@ public final class ProfilesConsole implements ServerComponent {
}
return null;
}
+
+ public ProfileImporter getProfileImporter(String exporterKey) {
+ for (ProfileImporter importer : profileImporters) {
+ if (StringUtils.equals(exporterKey, importer.getKey())) {
+ return importer;
+ }
+ }
+ return null;
+ }
}
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 9654bd04bbf..404e2dac725 100644
--- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -26,10 +26,11 @@ import org.sonar.api.Plugin;
import org.sonar.api.Plugins;
import org.sonar.api.Property;
import org.sonar.api.profiles.ProfileExporter;
+import org.sonar.api.profiles.ProfileImporter;
import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
import org.sonar.api.rules.DefaultRulesManager;
import org.sonar.api.rules.RuleRepository;
+import org.sonar.api.utils.ValidationMessages;
import org.sonar.api.web.*;
import org.sonar.jpa.dao.AsyncMeasuresService;
import org.sonar.jpa.dialect.Dialect;
@@ -50,7 +51,6 @@ import org.sonar.updatecenter.common.Version;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
public class JRubyFacade {
@@ -123,9 +123,10 @@ public class JRubyFacade {
return getContainer().getComponent(Plugins.class).getPlugins();
}
- public List<Plugin> getPluginsWithConfigurationImportable(Language language) {
- return getRulesManager().getImportablePlugins(language);
- }
+
+
+
+ /* PROFILES CONSOLE : RULES AND METRIC THRESHOLDS */
public List<RuleRepository> getRuleRepositoriesByLanguage(String languageKey) {
return getContainer().getComponent(RulesConsole.class).getRepositoriesByLanguage(languageKey);
@@ -135,20 +136,28 @@ public class JRubyFacade {
return getContainer().getComponent(ProfilesConsole.class).backupProfile(profileId);
}
+ public ValidationMessages restoreProfile(String profileName, String language, String xmlBackup) {
+ return getContainer().getComponent(ProfilesConsole.class).restoreProfile(profileName, language, xmlBackup);
+ }
+
public List<ProfileExporter> getProfileExportersForLanguage(String language) {
return getContainer().getComponent(ProfilesConsole.class).getProfileExportersForLanguage(language);
}
+ public List<ProfileImporter> getProfileImportersForLanguage(String language) {
+ return getContainer().getComponent(ProfilesConsole.class).getProfileImportersForLanguage(language);
+ }
+
public String exportProfile(int profileId, String exporterKey) {
return getContainer().getComponent(ProfilesConsole.class).exportProfile(profileId, exporterKey);
}
- public String getProfileExporterMimeType(String exporterKey) {
- return getContainer().getComponent(ProfilesConsole.class).getProfileExporter(exporterKey).getMimeType();
+ public ValidationMessages importProfile(int profileId, String importerKey, String fileContent) {
+ return getContainer().getComponent(ProfilesConsole.class).importProfile(profileId, importerKey, fileContent);
}
- public void importConfiguration(String pluginKey, long profileId, String configuration) {
- getProfilesManager().importProfile(pluginKey, (int) profileId, configuration);
+ public String getProfileExporterMimeType(String exporterKey) {
+ return getContainer().getComponent(ProfilesConsole.class).getProfileExporter(exporterKey).getMimeType();
}
public void copyProfile(long profileId, String newProfileName) {
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/alerts_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/alerts_controller.rb
index 9015845ae74..487b371d866 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/alerts_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/alerts_controller.rb
@@ -20,45 +20,39 @@
class AlertsController < ApplicationController
SECTION=Navigation::SECTION_CONFIGURATION
+ verify :method => :post, :only => ['create', 'update', 'delete'], :redirect_to => { :action => 'index' }
before_filter :admin_required, :except => [ 'index' ]
- before_filter :load_profile
-
- protected
-
- def load_profile
- @profile = Profile.find(params[:profile_id])
- end
-
- public
-
- # GET /profiles/:profile_id/alerts
- # GET /profiles/:profile_id/alerts.xml
+
+
+ #
+ #
+ # GET /alerts/index/<profile_id>
+ #
+ #
def index
+ @profile = Profile.find(params[:id])
@alerts = @profile.alerts.sort
@alert=Alert.new
-
- respond_to do |format|
- format.html # index.html.erb
- format.xml { render :xml => @alerts }
- end
end
- # GET /profiles/:profile_id/alerts/1
- # GET /profiles/:profile_id/alerts/1.xml
+ #
+ #
+ # GET /alerts/show/<alert id>
+ #
+ #
def show
@alert = @profile.alerts.find(params[:id])
-
- respond_to do |format|
- format.html # show.html.erb
- format.xml { render :xml => @alert }
- end
end
- # GET /profiles/:profile_id/alerts/new
- # GET /profiles/:profile_id/alerts/new.xml
+
+ #
+ #
+ # GET /alerts/new?profile_id=<profile id>
+ #
+ #
def new
+ @profile = Profile.find(params[:profile_id])
@alert = @profile.alerts.build(params[:alert])
-
respond_to do |format|
format.js {
render :update do |page|
@@ -70,23 +64,32 @@ class AlertsController < ApplicationController
end
end
- # GET /profiles/:profile_id/alerts/edit
+
+ #
+ #
+ # GET /alerts/edit/<alert id>
+ #
+ #
def edit
@alert = @profile.alerts.find(params[:id])
end
- # POST /profiles/:profile_id/alerts
- # POST /profiles/:profile_id/alerts.xml
+
+ #
+ #
+ # POST /alerts/create?profile_id=<profile id>&...
+ #
+ #
def create
+ @profile = Profile.find(params[:profile_id])
@alert = @profile.alerts.build(params[:alert])
respond_to do |format|
if @alert.save
flash[:notice] = 'Alert is created.'
- format.html { redirect_to profile_alerts_path(@profile) }
- format.xml { render :xml => @alert, :status => :created, :location => profile_alert_path(profile, @alert) }
+ format.html { redirect_to :action => 'index', :id=>@profile.id }
format.js { render :update do |page|
- page.redirect_to profile_alerts_path(@profile)
+ page.redirect_to :action => 'index', :id=>@profile.id
end}
else
@@ -101,18 +104,22 @@ class AlertsController < ApplicationController
end
end
- # PUT /profiles/:profile_id/alerts/1
- # PUT /profiles/:profile_id/alerts/1.xml
+ #
+ #
+ # POST /alerts/update/<alert id>?profile_id=<profile id>
+ #
+ #
def update
+ @profile = Profile.find(params[:profile_id])
@alerts=@profile.alerts
alert = @alerts.find(params[:id])
respond_to do |format|
if alert.update_attributes(params[:alert])
flash[:notice] = 'Alert is updated.'
- format.html { redirect_to profile_alerts_path(@profile) }
+ format.html { redirect_to :action => 'index', :id=>@profile.id }
format.xml { head :ok }
- format.js { render :update do |page| page.redirect_to profile_alerts_path(@profile) end}
+ format.js { render :update do |page| page.redirect_to :action => 'index', :id=>@profile.id end}
else
@alert=Alert.new
format.html { render :action => "index" }
@@ -124,16 +131,21 @@ class AlertsController < ApplicationController
end
end
- # DELETE /profiles/:profile_id/alerts/1
- # DELETE /profiles/:profile_id/alerts/1.xml
- def destroy
+ #
+ #
+ # POST /alerts/delete/<alert id>?profile_id=<profile id>
+ #
+ #
+ def delete
+ @profile = Profile.find(params[:profile_id])
@alert = @profile.alerts.find(params[:id])
@alert.destroy
flash[:notice] = 'Alert is deleted.'
respond_to do |format|
- format.html { redirect_to(profile_alerts_path(@profile)) }
+ format.html { redirect_to(:action => 'index', :id=>@profile.id) }
format.xml { head :ok }
end
end
+
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb
index 033a2e87a4c..097947ba703 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb
@@ -19,114 +19,90 @@
#
class ProfilesController < ApplicationController
SECTION=Navigation::SECTION_CONFIGURATION
-
+
+ verify :method => :post, :only => ['create', 'delete', 'copy', 'set_as_default', 'restore', 'backup', 'set_projects'], :redirect_to => { :action => 'index' }
before_filter :admin_required, :except => [ 'index', 'show', 'projects' ]
- # GET /profiles
- # GET /profiles.xml
+ #
+ #
+ # GET /profiles/index
+ #
+ #
def index
- @profiles = Profile.find(:all, :order => 'name')
-
- respond_to do |format|
- format.html # index.html.erb
- format.xml { render :xml => @profiles }
- end
+ @profiles = Profile.find(:all, :order => 'name')
end
- # GET /profiles/1
- # GET /profiles/1.xml
+
+ #
+ #
+ # GET /profiles/show/<id>
+ #
+ #
def show
@profile = Profile.find(params[:id])
-
- respond_to do |format|
- format.html # show.html.erb
- format.xml { render :xml => @profile }
- end
end
- # GET /profiles/new
- # GET /profiles/new.xml
- def new
- @profile = Profile.new
- @plugins_by_language = {}
- java_facade.getLanguages().each do |language|
- @plugins_by_language[language.getKey()] = java_facade.getPluginsWithConfigurationImportable(language)
- end
- respond_to do |format|
- format.js {
- render :update do |page|
- page.replace_html('create_profile_row', :partial => 'new')
- end
- }
- format.html # new.html.erb
- format.xml { render :xml => @profile }
- end
- end
-
- # GET /profiles/1/edit
- def edit
- @profile = Profile.find(params[:id])
- end
+ #
+ #
+ # POST /profiles/create?name=<profile name>&language=<language>
+ #
+ #
def create
- profile = RulesProfile.new(:name => params[:name], :language => params[:language], :default_profile => false)
- profile.save
- if profile.errors.empty?
- java_facade.getLanguages().select{|l| l.getKey()==profile.language}.each do |language|
- java_facade.getPluginsWithConfigurationImportable(language).each do |plugin|
- file = params[plugin.getKey()]
- configuration = read_configuration(file)
- if not configuration.nil?
- import_configuration(profile.id, configuration, plugin.getKey())
+ profile_name=params[:name]
+ language=params[:language]
+ if profile_name.blank?|| language.blank?
+ flash[:warning]='Please type a profile name.'
+ else
+ profile=Profile.find(:first, :conditions => {:name => profile_name, :language => language})
+ if profile
+ flash[:error]="This profile already exists: #{profile_name}"
+
+ else
+ profile = Profile.create(:name => profile_name, :language => language, :default_profile => false)
+ ok=profile.errors.empty?
+ if ok && params[:backup]
+ params[:backup].each_pair do |importer_key, file|
+ if !file.blank? && ok
+ messages = java_facade.importProfile(profile.id, importer_key, read_file_param(file))
+ flash_validation_messages(messages)
+ ok &= !messages.hasErrors()
+ end
end
end
+ if ok
+ flash[:notice]= "Profile '#{profile.name}' created. Set it as default or link it to a project to use it for next measures."
+ else
+ profile.reload
+ profile.destroy
+ end
end
- flash[:notice]= "Profile '#{profile.name}' created. Set it as default or link it to a project to use it for next measures."
- else
- flash[:error] = profile.errors.full_messages.first
end
redirect_to :action => 'index'
-
- rescue NativeException
- profile.destroy
- flash[:error] = "No profile created. Please check your configuration files."
- redirect_to :action => 'index'
end
- # PUT /profiles/1
- # PUT /profiles/1.xml
- def update
- @profile = Profile.find(params[:id])
-
- respond_to do |format|
- if @profile.update_attributes(params[:profile])
- flash[:notice] = 'Profile was successfully updated.'
- format.html { redirect_to(@profile) }
- format.xml { head :ok }
- else
- format.html { render :action => "edit" }
- format.xml { render :xml => @profile.errors, :status => :unprocessable_entity }
- end
- end
- end
-
- # DELETE /profiles/1
- # DELETE /profiles/1.xml
- def destroy
+ #
+ #
+ # POST /profiles/delete/<id>
+ #
+ #
+ def delete
@profile = Profile.find(params[:id])
if @profile && !@profile.provided? && !@profile.default_profile?
java_facade.deleteProfile(@profile.id)
flash[:notice]="Profile '#{@profile.name}' is deleted."
end
-
- respond_to do |format|
- format.html { redirect_to(:controller => 'profiles', :action => 'index') }
- format.xml { head :ok }
- end
+ redirect_to(:controller => 'profiles', :action => 'index')
end
+
+ #
+ #
+ # POST /profiles/set_as_default/<id>
+ #
+ #
def set_as_default
profile = Profile.find(params[:id])
profile.set_as_default
@@ -134,6 +110,13 @@ class ProfilesController < ApplicationController
redirect_to :action => 'index'
end
+
+
+ #
+ #
+ # POST /profiles/copy/<id>?name=<name of new profile>
+ #
+ #
def copy
profile = Profile.find(params[:id])
name = params['copy_' + profile.id.to_s]
@@ -149,6 +132,67 @@ class ProfilesController < ApplicationController
redirect_to :action => 'index'
end
+
+
+ #
+ #
+ # POST /profiles/backup/<id>
+ #
+ #
+ def backup
+ profile = Profile.find(params[:id])
+ xml = java_facade.backupProfile(profile.id)
+ send_data(xml, :type => 'text/xml', :disposition => "attachment; filename=#{profile.name}_#{profile.language}.xml")
+ end
+
+
+
+ #
+ #
+ # POST /profiles/restore/<id>
+ #
+ #
+ def restore
+ profile_name=params[:name]
+ language=params[:language]
+ profile=Profile.find(:first, :conditions => {:name => profile_name, :language => language})
+ if profile
+ flash[:error]='An existing profile can not be restored. Please delete it before.'
+ elsif params[:backup].blank?
+ flash[:warning]='Please upload a backup file.'
+ else
+ messages=java_facade.restoreProfile(profile_name, language, read_file_param(params[:backup]))
+ flash_validation_messages(messages)
+ end
+ redirect_to :action => 'index'
+ end
+
+
+
+ #
+ #
+ # GET /profiles/export?name=<profile name>&language=<language>&format<exporter key>
+ #
+ #
+ def export
+ name = CGI::unescape(params[:name])
+ language = params[:language]
+ if (name != 'active')
+ profile = Profile.find_by_name_and_language(name, language)
+ else
+ profile = Profile.find_active_profile_by_language(language)
+ end
+ exporter_key = params[:format]
+ result = java_facade.exportProfile(profile.id, exporter_key)
+ send_data(result, :type => java_facade.getProfileExporterMimeType(exporter_key), :disposition => 'inline')
+ end
+
+
+ #
+ #
+ # GET /profiles/projects/<id>
+ #
+ #
def projects
@profile = Profile.find(params[:id])
@available_projects=Project.find(:all,
@@ -158,7 +202,13 @@ class ProfilesController < ApplicationController
@available_projects-=@profile.projects
end
-
+
+
+ #
+ #
+ # POST /profiles/set_projects/<id>?projects=<project ids>
+ #
+ #
def set_projects
@profile = Profile.find(params[:id])
@profile.projects.clear
@@ -169,17 +219,10 @@ class ProfilesController < ApplicationController
redirect_to :action => 'projects', :id => @profile.id
end
+
private
- def language_names_by_key
- languages_by_key = {}
- java_facade.getLanguages().each do |language|
- languages_by_key[language.getKey()] = language.getName()
- end
- languages_by_key
- end
-
- def read_configuration(configuration_file)
+ def read_file_param(configuration_file)
# configuration file is a StringIO
if configuration_file.respond_to?(:read)
return configuration_file.read
@@ -188,7 +231,15 @@ class ProfilesController < ApplicationController
nil
end
- def import_configuration(profile_id, configuration, plugin_key)
- java_facade.importConfiguration(plugin_key, profile_id, configuration)
+ def flash_validation_messages(messages)
+ if messages.hasErrors()
+ flash[:error]=messages.getErrors().map{|m| m.getLabel()}.join('<br/>')
+ end
+ if messages.hasWarnings()
+ flash[:warning]=messages.getWarnings().map{|m| m.getLabel()}.join('<br/>')
+ end
+ if messages.hasInfos()
+ flash[:notice]=messages.getInfos().map{|m| m.getLabel()}.join('<br/>')
+ end
end
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/rules_configuration_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/rules_configuration_controller.rb
index 580ed989693..949402c6a34 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/rules_configuration_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/rules_configuration_controller.rb
@@ -29,7 +29,7 @@ class RulesConfigurationController < ApplicationController
RULE_PRIORITIES = Sonar::RulePriority.as_options.reverse
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
- verify :method => :post, :only => ['activate_rule', 'update_param', 'bulk_edit', 'create', 'update', 'delete', 'backup'], :redirect_to => { :action => 'index' }
+ verify :method => :post, :only => ['activate_rule', 'update_param', 'bulk_edit', 'create', 'update', 'delete'], :redirect_to => { :action => 'index' }
before_filter :admin_required, :except => [ 'index', 'export' ]
@@ -252,37 +252,6 @@ class RulesConfigurationController < ApplicationController
end
- #
- #
- # POST /rules_configuration/backup?id=<profile_id>
- #
- #
- def backup
- profile = RulesProfile.find(params[:id])
- xml = java_facade.backupProfile(profile.id)
- send_data(xml, :type => 'text/xml', :disposition => "attachment; filename=#{profile.name}_#{profile.language}.xml")
- end
-
-
-
- #
- #
- # GET /rules_configuration/export?name=<profile name>&language=<language>&format<exporter key>
- #
- #
- def export
- name = CGI::unescape(params[:name])
- language = params[:language]
- if (name != 'active')
- profile = RulesProfile.find_by_name_and_language(name, language)
- else
- profile = RulesProfile.find_active_profile_by_language(language)
- end
- exporter_key = params[:format]
- result = java_facade.exportProfile(profile.id, exporter_key)
- send_data(result, :type => java_facade.getProfileExporterMimeType(exporter_key), :disposition => 'inline')
- end
-
def update_param
is_admin=true # security has already been checked by controller filters
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/alerts_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/alerts_helper.rb
index eab126a02eb..e86b999221a 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/alerts_helper.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/alerts_helper.rb
@@ -25,7 +25,7 @@ module AlertsHelper
def operators_for_select(alert)
if alert.metric.nil?
- []
+ {}
elsif alert.metric.numeric?
NUMERIC_THRESOLD_OPERATORS
@@ -38,7 +38,7 @@ module AlertsHelper
elsif alert.metric.val_type==Metric::VALUE_TYPE_LEVEL
LEVEL_THRESOLD_OPERATORS
else
- []
+ {}
end
end
@@ -68,23 +68,23 @@ module AlertsHelper
end
- def value_field(form, alert, fieldname)
+ def value_field(alert, value, fieldname)
if alert.metric.nil?
- form.text_field fieldname, :size => 5
+ text_field_tag fieldname, value, :size => 5
elsif alert.metric.numeric?
- form.text_field fieldname, :size => 5
+ text_field_tag fieldname, value, :size => 5
elsif alert.metric.val_type==Metric::VALUE_TYPE_BOOLEAN
- form.select fieldname, {'' => '', 'Yes' => '1', 'No' => '0'}
+ select_tag fieldname, {'' => '', 'Yes' => '1', 'No' => '0'}
elsif alert.metric.val_type==Metric::VALUE_TYPE_STRING
- form.text_field fieldname, :size => 5
+ text_field_tag fieldname, value, :size => 5
elsif alert.metric.val_type==Metric::VALUE_TYPE_LEVEL
- form.select fieldname, {'' => '', 'OK' => Metric::TYPE_LEVEL_OK, 'Error' => Metric::TYPE_LEVEL_ERROR, 'Warning' => Metric::TYPE_LEVEL_WARN}
+ select_tag fieldname, {'' => '', 'OK' => Metric::TYPE_LEVEL_OK, 'Error' => Metric::TYPE_LEVEL_ERROR, 'Warning' => Metric::TYPE_LEVEL_WARN}
else
- form.hidden_field fieldname
+ hidden_field_tag fieldname, value
end
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb
index d93bf560f35..bf615b3fc64 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/profile.rb
@@ -48,6 +48,14 @@ class Profile < ActiveRecord::Base
new_rule_profile.errors
end
+ def self.find_by_name_and_language(name, language)
+ Profile.find(:first, :conditions => {:name => name, :language => language})
+ end
+
+ def self.find_active_profile_by_language(language)
+ Profile.find(:first, :conditions => {:default_profile => true, :language => language})
+ end
+
def self.default_profile
Profile.find(:first, :conditions => {:default_profile => true})
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_edit.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_edit.html.erb
index efb51c0ab99..47c531322a3 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_edit.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_edit.html.erb
@@ -1,23 +1,27 @@
-<% remote_form_for([@profile, alert]) do |f| %>
+<%= form_remote_tag :url => {:action => 'update', :id => alert.id, :profile_id => @profile.id}, :html => {:id => "edit_alert_#{alert.id}"} %>
<table>
<tr>
<td nowrap>
<%= h alert.name %>
</td>
<td width="10%" nowrap>
- <%= f.select 'operator', operators_for_select(alert) %>
+ <select id="alert_operator" name="alert[operator]">
+ <% operators_for_select(alert).each_pair do |name, key| %>
+ <option value="<%= key -%>" <%= 'selected' if alert.operator==key -%>><%= name -%></option>
+ <% end %>
+ </select>
</td>
<td width="20%" align="left">
<%= image_tag 'levels/warn.png', :alt => 'Warning threshold' %>
- <%= value_field(f, alert, 'value_warning') %> <%= alert.metric.suffix if alert.metric %>
+ <%= value_field(alert, alert.value_warning, 'alert[value_warning]') %> <%= alert.metric.suffix if alert.metric %>
</td>
<td width="20%" align="left">
<%= image_tag 'levels/error.png', :alt => 'Error threshold' %>
- <%= value_field(f, alert, 'value_error') %> <%= alert.metric.suffix if alert.metric %>
+ <%= value_field(alert, alert.value_error, 'alert[value_error]') %> <%= alert.metric.suffix if alert.metric %>
</td>
<td width="120px" nowrap>
- <%= f.submit "Update" %>
- <%= link_to 'Delete', [@profile, alert], :confirm => 'Are you sure?', :method => :delete, :class => 'action', :id => "delete_#{u alert.name}" %>
+ <input id="alert_submit" type="submit" value="Update"></input>
+ <%= link_to 'Delete', {:action => 'delete', :id => alert.id, :profile_id =>@profile.id}, :confirm => 'Are you sure?', :method => :post, :class => 'action', :id => "delete_#{u alert.name}" %>
</td>
</tr>
</table>
@@ -30,4 +34,4 @@
</ul>
</div>
<% end %>
-<% end %> \ No newline at end of file
+</form> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_new.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_new.html.erb
index 0720f325cd8..a96fe2aa5cf 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_new.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_new.html.erb
@@ -1,5 +1,5 @@
<h3>Create alert</h3>
-<% remote_form_for [@profile, @alert] do |f| %>
+<%= form_remote_tag :url => {:action => 'create', :profile_id => @profile.id}, :html => {:id => 'new_alert_form'} %>
<table class="spaced">
<tr>
<td valign="top">
@@ -24,25 +24,31 @@
</td>
<td width="10%" valign="top">
- <%= f.select 'operator', operators_for_select(@alert), :selected => default_operator(@alert) %>
+ <select id="alert_operator" name="alert[operator]">
+ <%
+ default_op=default_operator(@alert)
+ operators_for_select(@alert).each_pair do |name, key| %>
+ <option value="<%= key -%>" <%= 'selected' if default_op==key -%>><%= name -%></option>
+ <% end %>
+ </select>
</td>
<td width="20%" valign="top">
<%= image_tag 'levels/warn.png', :alt => 'A warning is triggered when this value is reached.' %>
- <%= value_field(f, @alert, 'value_warning') %>
+ <%= value_field(@alert, '', 'alert[value_warning]') %>
<%= @alert.metric.suffix if @alert.metric %><br/>
<span class="note">Warning threshold</span>
</td>
<td width="20%" valign="top">
<%= image_tag 'levels/error.png', :alt => 'An error is triggered when this value is reached.' %>
- <%= value_field(f, @alert, 'value_error') %>
+ <%= value_field(@alert, '', 'alert[value_error]') %>
<%= @alert.metric.suffix if @alert.metric %><br/>
<span class="note">Error threshold</span>
</td>
<td width="120px" nowrap valign="top">
- <%= f.submit 'Create', :id =>"submit_create" %>
+ <input type="submit" value="Create" id="submit_create"></input>
</td>
</tr>
</table>
@@ -55,4 +61,4 @@
</ul>
</div>
<% end %>
-<% end %>
+</form>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_show.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_show.html.erb
index df96e553a14..77ee3fb5c89 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_show.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/_show.html.erb
@@ -7,13 +7,13 @@
<%= operators_for_select(alert).invert[alert.operator] %>
</td>
<td width="20%">
- <% if alert.metric and not alert.value_warning.blank? %>
+ <% if alert.metric && !alert.value_warning.blank? %>
<%= image_tag 'levels/warn.png', :alt => 'Warning threshold' %>
<%= alert.value_warning %> <%= alert.metric.suffix if alert.metric %>
<% end %>
</td>
<td width="20%">
- <% if alert.metric and not alert.value_error.blank? %>
+ <% if alert.metric && !alert.value_error.blank? %>
<%= image_tag 'levels/error.png', :alt => 'Error threshold' %>
<%= alert.value_error %> <%= alert.metric.suffix %>
<% end %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/index.html.erb
index 8b2199cb4a9..6eeebbdf823 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/index.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/alerts/index.html.erb
@@ -1,4 +1,4 @@
-<h1 class="marginbottom10"><%= link_to 'Quality profiles', profiles_path %> / <%= h @profile.language -%> / <%= h @profile.name %></h1>
+<h1 class="marginbottom10"><%= link_to 'Quality profiles', :controller => 'profiles', :action => 'index' -%> / <%= h @profile.language -%> / <%= h @profile.name %></h1>
<%= render :partial => 'profiles/tabs', :locals => {:selected_tab=>'Alerts'} %>
<% if is_admin? %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/_tabs.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/_tabs.html.erb
index e02c19a8bf0..6eda63ef204 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/_tabs.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/_tabs.html.erb
@@ -7,7 +7,7 @@
<a href="<%= url_for :controller => 'rules_configuration', :action => 'index', :id => @profile.id %>" <%= "class='selected'" if selected_tab=='Coding rules' -%>>Coding rules</a>
</li>
<li>
- <a href="<%= profile_alerts_path @profile.id -%>" <%= "class='selected'" if selected_tab=='Alerts' -%>>Alerts</a>
+ <a href="<%= url_for :controller => 'alerts', :action => 'index', :id => @profile.id -%>" <%= "class='selected'" if selected_tab=='Alerts' -%>>Alerts</a>
</li>
<li>
<a href="<%= url_for :controller => 'profiles', :action => 'projects', :id => @profile.id -%>" <%= "class='selected'" if selected_tab=='Projects' -%>>Projects</a>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb
index a4bbfd86301..4beb03a9591 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb
@@ -1,6 +1,7 @@
<%
languages.each do |language|
- exporters=controller.java_facade.getProfileExportersForLanguage(language.getKey())
+ exporters=controller.java_facade.getProfileExportersForLanguage(language.getKey())
+ importers=controller.java_facade.getProfileImportersForLanguage(language.getKey())
%>
<table class="data2 width100 marginbottom10" id="profiles_<%= language.getKey() -%>">
@@ -8,15 +9,25 @@
<tr>
<th><h2><%= language.getName() %></h2></th>
<th class="right"></th>
+ <th class="right"></th>
<th class="right">Export</th>
- <th class="right">Alerts</th>
<th class="right">Default</th>
<th class="right">Projects</th>
- <th width="1%"> </th>
- <th width="1%"> </th>
- <th width="1%"> </th>
+ <th width="1%" class="right" colspan="3">Operations</th>
</tr>
</thead>
+ <% if administrator? %>
+ <tfoot id="footer-<%= language.getKey() -%>">
+ <tr>
+ <td colspan="9">
+ <a href="#" onClick="$('footer-<%= language.getKey() -%>').hide();$('create-form-<%= language.getKey() -%>').show();return false;">Create <%= language.getName() -%> profile</a>
+ |
+ <a href="#" onclick="$('footer-<%= language.getKey() -%>').hide();$('restore-form-<%= language.getKey() -%>').show();return false;">Restore <%= language.getName() -%> profile</a>
+ </td>
+ </tr>
+ </tfoot>
+ <% end %>
+ <tbody>
<% @profiles.select{|p| p.language==language.getKey()}.each do |profile| %>
<tr class="<%= cycle 'even', 'odd', :name => language.getKey() -%>" id="<%= u profile.key %>">
<td><a href="<%= url_for :controller => 'rules_configuration', :action => 'index', :id => profile.id -%>"><%= h profile.name %></a></td>
@@ -26,18 +37,17 @@
<span id="activated_rules_<%= u profile.key -%>"><%= profile.active_rules.count -%> rules</span>
</a>
</td>
+
+ <td align="right"><%= link_to pluralize(profile.alerts.size, 'alert'), url_for(:controller => 'alerts', :action => 'index', :id => profile.id), :id => "alerts_#{u profile.key}" %></td>
<td align="right">
<% exporters.each do |exporter| %>
<%= link_to exporter.getName(),
- {:controller => 'rules_configuration', :action => 'export', :language => profile.language,
- :name => url_encode(profile.name), :format => exporter.getKey()},
- :id => "export_" + exporter.getKey().to_s + "_" + u(profile.key) %>
+ {:action => 'export', :language => profile.language, :name => url_encode(profile.name), :format => exporter.getKey()}, :id => "export_" + exporter.getKey().to_s + "_" + u(profile.key) %>
<% end %>
</td>
- <td align="right"><%= link_to pluralize(profile.alerts.size, 'alert'), profile_alerts_path(profile), :id => "alerts_#{u profile.key}" %></td>
-
+
<td align="right">
<% if (!profile.default_profile? && administrator?) %>
<%= button_to 'Set as default', { :action => 'set_as_default', :id => profile.id }, :class => 'action',
@@ -61,8 +71,7 @@
<td align="right">
<% if (!profile.provided? && administrator?) %>
- <% form_tag(:controller => 'rules_configuration', :action => 'backup', :id => profile.id) do -%>
- <%= hidden_field_tag 'backup_' + profile.id.to_s %>
+ <% form_tag(:action => 'backup', :id => profile.id) do -%>
<input type="submit" name="button_backup" id="backup_<%= u profile.key %>" value="Backup"></input>
<% end
end %>
@@ -79,33 +88,62 @@
<td>
<% if (!(profile.provided?) && !(profile.default_profile?) && administrator?) %>
- <%= button_to "Delete", { :action => 'destroy', :id => profile.id }, :class => 'action',
+ <%= button_to "Delete", { :action => 'delete', :id => profile.id }, :class => 'action',
:id => "delete_#{u profile.key}",
:confirm => "Are you sure that you want to delete the profile '#{profile.name}' ?",
- :method => :delete %>
+ :method => :post %>
<% end %>
</td>
</tr>
<% end %>
+ </tbody>
</table>
-<% end %>
-<% if administrator? %>
-<div id="create_profile_row">
- <% form_remote_tag :url => {:action => 'new'} do %>
- <%= submit_tag 'Create profile', :id => 'create_profile' %>
- <% end %>
-</div>
-<% end %>
+ <% if administrator? %>
+ <form class="admin" id="create-form-<%= language.getKey()-%>" action="<%= url_for :action => 'create' -%>" style="display: none" enctype="multipart/form-data" method="post">
+ <input type="hidden" name="language" value="<%= language.getKey() -%>"></input>
+ <table class="spaced width100">
+ <tr>
+ <td width="1%" nowrap>Name: </td>
+ <td><input type="text" name="name"></input></td>
+ </tr>
+ <% importers.each do |importer| %>
+ <tr>
+ <td width="1%" nowrap><%= importer.getName() -%>: </td>
+ <td>
+ <%= file_field_tag "backup[#{importer.getKey()}]" %></input>
+ <span class="note">Optional configuration file</span>
+ </td>
+ </tr>
+ <% end %>
-<script type="text/javascript">
- function changeLanguage(){
- $$('.language_class').each(function(element) {
- element.style.display = 'none';
- });
- elementTable = $($('language').value);
- if (elementTable){
- elementTable.toggle();
- }
- };
-</script>
+ <tr>
+ <td colspan="2">
+ <input type="submit" value="Create <%= language.getName() %> profile"></input>
+ <a href="#" onclick="$('create-form-<%= language.getKey()-%>').reset();$('create-form-<%= language.getKey()-%>').hide();$('footer-<%= language.getKey()-%>').show();return false;">Cancel</a>
+ </td>
+ </tr>
+ </table>
+ </form>
+ <form class="admin" id="restore-form-<%= language.getKey()-%>" action="<%= url_for :action => 'restore' -%>" enctype="multipart/form-data" style="display: none" method="post">
+ <input type="hidden" name="language" value="<%= language.getKey() -%>"></input>
+ <table class="spaced width100">
+ <tr>
+ <td width="1%" nowrap>Name: </td>
+ <td><input type="text" name="name"></input></td>
+ </tr>
+ <tr>
+ <td width="1%" nowrap>Backup: </td>
+ <td><%= file_field_tag 'backup' %></td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <input type="submit" value="Restore <%= language.getName() %> profile"></input>
+ <a href="#" onclick="$('restore-form-<%= language.getKey()-%>').reset();$('restore-form-<%= language.getKey()-%>').hide();$('footer-<%= language.getKey()-%>').show();return false;">Cancel</a>
+ </td>
+ </tr>
+ </table>
+ </form>
+ <% end %>
+<% end %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/projects.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/projects.html.erb
index c28977511a0..59afeb3554d 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/projects.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/projects.html.erb
@@ -1,4 +1,4 @@
-<h1 class="marginbottom10"><%= link_to 'Quality profiles', profiles_path %> / <%= h @profile.language -%> / <%= h @profile.name %></h1>
+<h1 class="marginbottom10"><%= link_to 'Quality profiles', :controller => 'profiles', :action => 'index' -%> / <%= h @profile.language -%> / <%= h @profile.name %></h1>
<%= render :partial => 'profiles/tabs', :locals => {:selected_tab=>'Projects'} %>
<div class="tabs-panel">
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/index.html.erb
index 8af20de052f..59fd4e04eb8 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/index.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/rules_configuration/index.html.erb
@@ -26,7 +26,7 @@
}
</script>
-<h1 class="marginbottom10"><%= link_to 'Quality profiles', profiles_path %> / <%= h @profile.language -%> / <%= h @profile.name %></h1>
+<h1 class="marginbottom10"><%= link_to 'Quality profiles', :controller => 'profiles', :action => 'index' -%> / <%= h @profile.language -%> / <%= h @profile.name %></h1>
<%= render :partial => 'profiles/tabs', :locals => {:selected_tab=>'Coding rules'} %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/config/routes.rb b/sonar-server/src/main/webapp/WEB-INF/config/routes.rb
index 7fef97fe5b0..1eb2b53a1f4 100644
--- a/sonar-server/src/main/webapp/WEB-INF/config/routes.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/config/routes.rb
@@ -13,9 +13,6 @@ ActionController::Routing::Routes.draw do |map|
api.resources :favorites, :only => [:index, :show, :create, :destroy], :requirements => { :id => /.*/ }
end
- map.resources :alerts
- map.resources :profiles, :has_many => :alerts
-
map.connect 'api/metrics', :controller => 'api/metrics', :action => 'index', :conditions => { :method => :get }
map.connect 'api/metrics/:id', :controller => 'api/metrics', :action => 'show', :conditions => { :method => :get }
map.connect 'api/metrics/:id', :controller => 'api/metrics', :action => 'create', :conditions => { :method => :post }