# SETTINGS
#
#------------------------------------------------------------------------------
+settings.add=Add value
settings.save_category=Save {0} Settings
property.category.email=Email
property.category.encryption=Encryption
*/
String[] options() default {};
+ /**
+ * Can the property take multiple values. Eg: list of email addresses.
+ *
+ * @since 3.3
+ */
+ boolean multiValues() default false;
}
private boolean onProject = false;
private boolean onModule = false;
private boolean isGlobal = true;
+ private boolean multiValues;
private PropertyDefinition(Property annotation) {
this.key = annotation.key();
this.category = annotation.category();
this.type = fixType(annotation.key(), annotation.type());
this.options = annotation.options();
+ this.multiValues = annotation.multiValues();
}
- private PropertyType fixType(String key, PropertyType type) {
+ private static PropertyType fixType(String key, PropertyType type) {
// Auto-detect passwords and licenses for old versions of plugins that
// do not declare property types
- PropertyType fix = type;
if (type == PropertyType.STRING) {
if (StringUtils.endsWith(key, ".password.secured")) {
- fix = PropertyType.PASSWORD;
+ return PropertyType.PASSWORD;
} else if (StringUtils.endsWith(key, ".license.secured")) {
- fix = PropertyType.LICENSE;
+ return PropertyType.LICENSE;
}
}
- return fix;
+ return type;
}
private PropertyDefinition(String key, PropertyType type, String[] options) {
}
public Result validate(@Nullable String value) {
- // TODO REFACTORING REQUIRED HERE
- Result result = Result.SUCCESS;
if (StringUtils.isNotBlank(value)) {
if (type == PropertyType.BOOLEAN) {
if (!StringUtils.equalsIgnoreCase(value, "true") && !StringUtils.equalsIgnoreCase(value, "false")) {
- result = Result.newError("notBoolean");
+ return Result.newError("notBoolean");
}
} else if (type == PropertyType.INTEGER) {
if (!NumberUtils.isDigits(value)) {
- result = Result.newError("notInteger");
+ return Result.newError("notInteger");
}
} else if (type == PropertyType.FLOAT) {
try {
Double.parseDouble(value);
} catch (NumberFormatException e) {
- result = Result.newError("notFloat");
+ return Result.newError("notFloat");
}
} else if (type == PropertyType.SINGLE_SELECT_LIST) {
if (!ArrayUtils.contains(options, value)) {
- result = Result.newError("notInOptions");
+ return Result.newError("notInOptions");
}
}
}
- return result;
+ return Result.SUCCESS;
}
public String getKey() {
public boolean isGlobal() {
return isGlobal;
}
+
+ /**
+ * @since 3.3
+ */
+ public boolean isMultiValues() {
+ return multiValues;
+ }
}
*/
package org.sonar.api.config;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.BatchComponent;
import org.sonar.api.Properties;
return definitions.values();
}
+ /**
+ * since 3.3
+ */
+ public Map<String, Collection<PropertyDefinition>> getGlobalPropertiesByCategory() {
+ Multimap<String, PropertyDefinition> byCategory = ArrayListMultimap.create();
+
+ for (PropertyDefinition definition : getAll()) {
+ if (definition.isGlobal()) {
+ byCategory.put(getCategory(definition.getKey()), definition);
+ }
+ }
+
+ return byCategory.asMap();
+ }
+
+ /**
+ * since 3.3
+ */
+ public Map<String, Collection<PropertyDefinition>> getProjectPropertiesByCategory() {
+ Multimap<String, PropertyDefinition> byCategory = ArrayListMultimap.create();
+
+ for (PropertyDefinition definition : getAll()) {
+ if (definition.isOnProject()) {
+ byCategory.put(getCategory(definition.getKey()), definition);
+ }
+ }
+
+ return byCategory.asMap();
+ }
+
+ /**
+ * since 3.3
+ */
+ public Map<String, Collection<PropertyDefinition>> getModulePropertiesByCategory() {
+ Multimap<String, PropertyDefinition> byCategory = ArrayListMultimap.create();
+
+ for (PropertyDefinition definition : getAll()) {
+ if (definition.isOnModule()) {
+ byCategory.put(getCategory(definition.getKey()), definition);
+ }
+ }
+
+ return byCategory.asMap();
+ }
+
public String getDefaultValue(String key) {
PropertyDefinition def = get(key);
- if (def != null) {
- return StringUtils.defaultIfEmpty(def.getDefaultValue(), null);
+ if (def == null) {
+ return null;
}
- return null;
+ return StringUtils.defaultIfEmpty(def.getDefaultValue(), null);
}
public String getCategory(String key) {
*/
package org.sonar.api.config;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.sonar.api.utils.DateUtils;
import javax.annotation.Nullable;
-import java.util.*;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
/**
* Project Settings on batch side, Global Settings on server side. This component does not access to database, so
* </ul>
*/
public final String[] getStringArray(String key) {
+ PropertyDefinition property = getDefinitions().get(key);
+ if ((null != property) && (property.isMultiValues())) {
+ String value = getString(key);
+ if (value == null) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+
+ List<String> values = Lists.newArrayList();
+ for (String v : Splitter.on(",").trimResults().split(value)) {
+ values.add(v.replace("%2C", ","));
+ }
+ return values.toArray(new String[values.size()]);
+ }
+
return getStringArrayBySeparator(key, ",");
}
return setProperty(key, newValue);
}
+ public final Settings setProperty(String key, @Nullable String[] values) {
+ PropertyDefinition property = getDefinitions().get(key);
+ if ((null == property) || (!property.isMultiValues())) {
+ throw new IllegalStateException("Fail to set multiple values on a single value property " + key);
+ }
+
+ if (values == null) {
+ properties.remove(key);
+ doOnRemoveProperty(key);
+ } else {
+ List<String> escaped = Lists.newArrayList();
+ for (String value : values) {
+ if (null != value) {
+ escaped.add(value.replace(",", "%2C"));
+ } else {
+ escaped.add("");
+ }
+ }
+
+ String escapedValue = Joiner.on(',').join(escaped);
+ properties.put(key, StringUtils.trim(escapedValue));
+ doOnSetProperty(key, escapedValue);
+ }
+ return this;
+ }
+
public final Settings setProperty(String key, @Nullable String value) {
if (value == null) {
properties.remove(key);
*/
package org.sonar.api.config;
-import org.hamcrest.core.Is;
import org.junit.Test;
import org.sonar.api.Properties;
import org.sonar.api.Property;
import org.sonar.api.PropertyType;
import org.sonar.api.utils.AnnotationUtils;
-import java.util.Arrays;
-
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
-import static org.junit.matchers.JUnitMatchers.hasItems;
+import static org.fest.assertions.Assertions.assertThat;
public class PropertyDefinitionTest {
-
@Test
public void createFromAnnotation() {
Properties props = AnnotationUtils.getAnnotation(Init.class, Properties.class);
PropertyDefinition def = PropertyDefinition.create(prop);
- assertThat(def.getKey(), Is.is("hello"));
- assertThat(def.getName(), Is.is("Hello"));
- assertThat(def.getDefaultValue(), Is.is("world"));
- assertThat(def.getCategory(), Is.is("categ"));
- assertThat(def.getOptions().length, Is.is(2));
- assertThat(Arrays.asList(def.getOptions()), hasItems("de", "en"));
- assertThat(def.getDescription(), Is.is("desc"));
- assertThat(def.getType(), Is.is(PropertyType.FLOAT));
- assertThat(def.isGlobal(), Is.is(false));
- assertThat(def.isOnProject(), Is.is(true));
- assertThat(def.isOnModule(), Is.is(true));
+ assertThat(def.getKey()).isEqualTo("hello");
+ assertThat(def.getName()).isEqualTo("Hello");
+ assertThat(def.getDefaultValue()).isEqualTo("world");
+ assertThat(def.getCategory()).isEqualTo("categ");
+ assertThat(def.getOptions()).hasSize(2);
+ assertThat(def.getOptions()).contains("de", "en");
+ assertThat(def.getDescription()).isEqualTo("desc");
+ assertThat(def.getType()).isEqualTo(PropertyType.FLOAT);
+ assertThat(def.isGlobal()).isFalse();
+ assertThat(def.isOnProject()).isTrue();
+ assertThat(def.isOnModule()).isTrue();
+ assertThat(def.isMultiValues()).isTrue();
}
- @Properties({
- @Property(key = "hello", name = "Hello", defaultValue = "world", description = "desc",
- options = {"de", "en"}, category = "categ", type = PropertyType.FLOAT, global = false, project = true, module = true)
- })
+ @Properties(@Property(key = "hello", name = "Hello", defaultValue = "world", description = "desc",
+ options = {"de", "en"}, category = "categ", type = PropertyType.FLOAT, global = false, project = true, module = true, multiValues = true))
static class Init {
}
PropertyDefinition def = PropertyDefinition.create(prop);
- assertThat(def.getKey(), Is.is("hello"));
- assertThat(def.getName(), Is.is("Hello"));
- assertThat(def.getDefaultValue(), Is.is(""));
- assertThat(def.getCategory(), Is.is(""));
- assertThat(def.getOptions().length, Is.is(0));
- assertThat(def.getDescription(), Is.is(""));
- assertThat(def.getType(), Is.is(PropertyType.STRING));
- assertThat(def.isGlobal(), Is.is(true));
- assertThat(def.isOnProject(), Is.is(false));
- assertThat(def.isOnModule(), Is.is(false));
+ assertThat(def.getKey()).isEqualTo("hello");
+ assertThat(def.getName()).isEqualTo("Hello");
+ assertThat(def.getDefaultValue()).isEmpty();
+ assertThat(def.getCategory()).isEmpty();
+ assertThat(def.getOptions()).isEmpty();
+ assertThat(def.getDescription()).isEmpty();
+ assertThat(def.getType()).isEqualTo(PropertyType.STRING);
+ assertThat(def.isGlobal()).isTrue();
+ assertThat(def.isOnProject()).isFalse();
+ assertThat(def.isOnModule()).isFalse();
+ assertThat(def.isMultiValues()).isFalse();
}
- @Properties({
- @Property(key = "hello", name = "Hello")
- })
+ @Properties(@Property(key = "hello", name = "Hello"))
static class DefaultValues {
}
public void validate_string() {
PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.STRING, new String[0]);
- assertThat(def.validate(null).isValid(), is(true));
- assertThat(def.validate("").isValid(), is(true));
- assertThat(def.validate(" ").isValid(), is(true));
- assertThat(def.validate("foo").isValid(), is(true));
+ assertThat(def.validate(null).isValid()).isTrue();
+ assertThat(def.validate("").isValid()).isTrue();
+ assertThat(def.validate(" ").isValid()).isTrue();
+ assertThat(def.validate("foo").isValid()).isTrue();
}
@Test
public void validate_boolean() {
PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.BOOLEAN, new String[0]);
- assertThat(def.validate(null).isValid(), is(true));
- assertThat(def.validate("").isValid(), is(true));
- assertThat(def.validate(" ").isValid(), is(true));
- assertThat(def.validate("true").isValid(), is(true));
- assertThat(def.validate("false").isValid(), is(true));
+ assertThat(def.validate(null).isValid()).isTrue();
+ assertThat(def.validate("").isValid()).isTrue();
+ assertThat(def.validate(" ").isValid()).isTrue();
+ assertThat(def.validate("true").isValid()).isTrue();
+ assertThat(def.validate("false").isValid()).isTrue();
- assertThat(def.validate("foo").isValid(), is(false));
- assertThat(def.validate("foo").getErrorKey(), is("notBoolean"));
+ assertThat(def.validate("foo").isValid()).isFalse();
+ assertThat(def.validate("foo").getErrorKey()).isEqualTo("notBoolean");
}
@Test
public void validate_integer() {
PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.INTEGER, new String[0]);
- assertThat(def.validate(null).isValid(), is(true));
- assertThat(def.validate("").isValid(), is(true));
- assertThat(def.validate(" ").isValid(), is(true));
- assertThat(def.validate("123456").isValid(), is(true));
+ assertThat(def.validate(null).isValid()).isTrue();
+ assertThat(def.validate("").isValid()).isTrue();
+ assertThat(def.validate(" ").isValid()).isTrue();
+ assertThat(def.validate("123456").isValid()).isTrue();
- assertThat(def.validate("foo").isValid(), is(false));
- assertThat(def.validate("foo").getErrorKey(), is("notInteger"));
+ assertThat(def.validate("foo").isValid()).isFalse();
+ assertThat(def.validate("foo").getErrorKey()).isEqualTo("notInteger");
}
@Test
public void validate_float() {
PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.FLOAT, new String[0]);
- assertThat(def.validate(null).isValid(), is(true));
- assertThat(def.validate("").isValid(), is(true));
- assertThat(def.validate(" ").isValid(), is(true));
- assertThat(def.validate("123456").isValid(), is(true));
- assertThat(def.validate("3.14").isValid(), is(true));
+ assertThat(def.validate(null).isValid()).isTrue();
+ assertThat(def.validate("").isValid()).isTrue();
+ assertThat(def.validate(" ").isValid()).isTrue();
+ assertThat(def.validate("123456").isValid()).isTrue();
+ assertThat(def.validate("3.14").isValid()).isTrue();
- assertThat(def.validate("foo").isValid(), is(false));
- assertThat(def.validate("foo").getErrorKey(), is("notFloat"));
+ assertThat(def.validate("foo").isValid()).isFalse();
+ assertThat(def.validate("foo").getErrorKey()).isEqualTo("notFloat");
}
@Test
public void validate_single_select_list() {
- PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.SINGLE_SELECT_LIST, new String[]{"de", "en"});
+ PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.SINGLE_SELECT_LIST, new String[] {"de", "en"});
- assertThat(def.validate(null).isValid(), is(true));
- assertThat(def.validate("").isValid(), is(true));
- assertThat(def.validate(" ").isValid(), is(true));
- assertThat(def.validate("de").isValid(), is(true));
- assertThat(def.validate("en").isValid(), is(true));
+ assertThat(def.validate(null).isValid()).isTrue();
+ assertThat(def.validate("").isValid()).isTrue();
+ assertThat(def.validate(" ").isValid()).isTrue();
+ assertThat(def.validate("de").isValid()).isTrue();
+ assertThat(def.validate("en").isValid()).isTrue();
- assertThat(def.validate("fr").isValid(), is(false));
- assertThat(def.validate("fr").getErrorKey(), is("notInOptions"));
+ assertThat(def.validate("fr").isValid()).isFalse();
+ assertThat(def.validate("fr").getErrorKey()).isEqualTo("notInOptions");
}
- @Properties({
- @Property(key = "scm.password.secured", name = "SCM password")
- })
+ @Properties(@Property(key = "scm.password.secured", name = "SCM password"))
static class OldScmPlugin {
}
PropertyDefinition def = PropertyDefinition.create(prop);
- assertThat(def.getKey(), Is.is("scm.password.secured"));
- assertThat(def.getType(), Is.is(PropertyType.PASSWORD));
+ assertThat(def.getKey()).isEqualTo("scm.password.secured");
+ assertThat(def.getType()).isEqualTo(PropertyType.PASSWORD);
}
- @Properties({
- @Property(key = "views.license.secured", name = "Views license")
- })
+ @Properties(@Property(key = "views.license.secured", name = "Views license"))
static class ViewsPlugin {
}
PropertyDefinition def = PropertyDefinition.create(prop);
- assertThat(def.getKey(), Is.is("views.license.secured"));
- assertThat(def.getType(), Is.is(PropertyType.LICENSE));
+ assertThat(def.getKey()).isEqualTo("views.license.secured");
+ assertThat(def.getType()).isEqualTo(PropertyType.LICENSE);
}
}
import org.sonar.api.Properties;
import org.sonar.api.Property;
+import static org.fest.assertions.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
})
static final class Categories {
}
+
+ @Test
+ public void should_group_by_category() {
+ PropertyDefinitions def = new PropertyDefinitions(ByCategory.class);
+
+ assertThat(def.getGlobalPropertiesByCategory().keySet()).containsOnly("catGlobal1", "catGlobal2");
+ assertThat(def.getProjectPropertiesByCategory().keySet()).containsOnly("catProject");
+ assertThat(def.getModulePropertiesByCategory().keySet()).containsOnly("catModule");
+ }
+
+ @Properties({
+ @Property(key = "global1", name = "Global1", category = "catGlobal1", global = true, project = false, module = false),
+ @Property(key = "global2", name = "Global2", category = "catGlobal1", global = true, project = false, module = false),
+ @Property(key = "global3", name = "Global3", category = "catGlobal2", global = true, project = false, module = false),
+ @Property(key = "project", name = "Project", category = "catProject", global = false, project = true, module = false),
+ @Property(key = "module", name = "Module", category = "catModule", global = false, project = false, module = true)
+ })
+ static final class ByCategory {
+ }
}
@Property(key = "boolean", name = "Boolean", defaultValue = "true"),
@Property(key = "falseboolean", name = "False Boolean", defaultValue = "false"),
@Property(key = "integer", name = "Integer", defaultValue = "12345"),
- @Property(key = "array", name = "Array", defaultValue = "one,two,three")
+ @Property(key = "array", name = "Array", defaultValue = "one,two,three"),
+ @Property(key = "multi_values", name = "Array", defaultValue = "1,2,3", multiValues = true)
})
static class Init {
}
assertThat(array).isEqualTo(new String[]{"one", "two", "three"});
}
+ @Test
+ public void setStringArray() {
+ Settings settings = new Settings(definitions);
+ settings.setProperty("multi_values", new String[] {"A", "B"});
+ String[] array = settings.getStringArray("multi_values");
+ assertThat(array).isEqualTo(new String[] {"A", "B"});
+ }
+
+ @Test
+ public void setStringArrayTrimValues() {
+ Settings settings = new Settings(definitions);
+ settings.setProperty("multi_values", new String[] {" A ", " B "});
+ String[] array = settings.getStringArray("multi_values");
+ assertThat(array).isEqualTo(new String[] {"A", "B"});
+ }
+
+ @Test
+ public void setStringArrayEscapeCommas() {
+ Settings settings = new Settings(definitions);
+ settings.setProperty("multi_values", new String[] {"A,B", "C,D"});
+ String[] array = settings.getStringArray("multi_values");
+ assertThat(array).isEqualTo(new String[] {"A,B", "C,D"});
+ }
+
+ @Test
+ public void setStringArrayWithEmptyValues() {
+ Settings settings = new Settings(definitions);
+ settings.setProperty("multi_values", new String[] {"A,B", "", "C,D"});
+ String[] array = settings.getStringArray("multi_values");
+ assertThat(array).isEqualTo(new String[] {"A,B", "", "C,D"});
+ }
+
+ @Test
+ public void setStringArrayWithNullValues() {
+ Settings settings = new Settings(definitions);
+ settings.setProperty("multi_values", new String[] {"A,B", null, "C,D"});
+ String[] array = settings.getStringArray("multi_values");
+ assertThat(array).isEqualTo(new String[] {"A,B", "", "C,D"});
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void shouldFailToSetArrayValueOnSingleValueProperty() {
+ Settings settings = new Settings(definitions);
+ settings.setProperty("array", new String[] {"A", "B", "C"});
+ }
+
@Test
public void getStringArray_no_value() {
Settings settings = new Settings();
end
def force_authentication?
- property = Property.find(:first, :conditions => {:prop_key => org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, :resource_id => nil, :user_id => nil})
+ property = Property.by_key(org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY)
property ? property.value == 'true' : false
end
# curl http://localhost:9000/api/user_properties/<key> -v -u admin:admin
#
def show
- property = Property.find(:first, :conditions => ['user_id=? and prop_key=?', current_user.id, params[:id]])
+ property = Property.by_key(params[:id], nil, current_user.id)
if property
respond_to do |format|
format.json { render :json => jsonp(properties_to_json([property])) }
value = params[:value]
if key
begin
- Property.delete_all(['prop_key=? AND user_id=?', key,current_user.id])
+ Property.clear(key, nil, current_user.id)
property=Property.create(:prop_key => key, :text_value => value, :user_id => current_user.id)
respond_to do |format|
format.json { render :json => jsonp(properties_to_json([property])) }
def destroy
begin
if params[:id]
- Property.delete_all(['prop_key=? AND user_id=?', params[:id], current_user.id])
+ Property.clear(params[:id], nil, current_user.id)
end
render_success("Property deleted")
-
rescue Exception => e
logger.error("Fails to execute #{request.url} : #{e.message}")
render_error(e.message)
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
#
class ProjectController < ApplicationController
- verify :method => :post, :only => [:set_links, :set_exclusions, :delete_exclusions, :update_key, :perform_key_bulk_update, :update_quality_profile],
+ verify :method => :post, :only => [:set_links, :set_exclusions, :delete_exclusions, :update_key, :perform_key_bulk_update, :update_quality_profile],
:redirect_to => {:action => :index}
verify :method => :delete, :only => [:delete], :redirect_to => {:action => :index}
if java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'deletable')
deletion_manager = ResourceDeletionManager.instance
- if deletion_manager.currently_deleting_resources? ||
+ if deletion_manager.currently_deleting_resources? ||
(!deletion_manager.currently_deleting_resources? && deletion_manager.deletion_failures_occured?)
# a deletion is happening or it has just finished with errors => display the message from the Resource Deletion Manager
render :template => 'project/pending_deletion'
- else
+ else
@snapshot=@project.last_snapshot
end
else
def delete
@project = get_current_project(params[:id])
-
+
# Ask the resource deletion manager to start the migration
# => this is an asynchronous AJAX call
ResourceDeletionManager.instance.delete_resources([@project.id])
-
+
# and return some text that will actually never be displayed
render :text => ResourceDeletionManager.instance.message
end
-
+
def pending_deletion
deletion_manager = ResourceDeletionManager.instance
-
- if deletion_manager.currently_deleting_resources? ||
+
+ if deletion_manager.currently_deleting_resources? ||
(!deletion_manager.currently_deleting_resources? && deletion_manager.deletion_failures_occured?)
# display the same page again and again
# => implicit render "pending_deletion.html.erb"
redirect_to_default
end
end
-
+
def dismiss_deletion_message
# It is important to reinit the ResourceDeletionManager so that the deletion screens can be available again
ResourceDeletionManager.instance.reinit
-
+
redirect_to :action => 'deletion', :id => params[:id]
end
-
+
def quality_profile
@project = get_current_project(params[:id])
@profiles = Profile.find(:all, :conditions => {:language => @project.language, :enabled => true})
end
-
+
def update_quality_profile
project = get_current_project(params[:id])
-
+
selected_profile = Profile.find(:first, :conditions => {:id => params[:quality_profile].to_i})
- if selected_profile && selected_profile.language == project.language
+ if selected_profile && selected_profile.language == project.language
project.profile = selected_profile
project.save!
flash[:notice] = message('project_quality_profile.profile_successfully_updated')
selected_profile_name = selected_profile ? selected_profile.name + "(" + selected_profile.language + ")" : "Unknown profile"
flash[:error] = message('project_quality_profile.project_cannot_be_update_with_profile_x', :params => selected_profile_name)
end
-
+
redirect_to :action => 'quality_profile', :id => project.id
end
-
+
def key
@project = get_current_project(params[:id])
end
-
+
def update_key
project = get_current_project(params[:id])
-
+
new_key = params[:new_key].strip
if new_key.blank?
flash[:error] = message('update_key.new_key_cant_be_blank_for_x', :params => project.key)
java_facade.updateResourceKey(project.id, new_key)
flash[:notice] = message('update_key.key_updated')
rescue Exception => e
- flash[:error] = message('update_key.error_occured_while_renaming_key_of_x',
+ flash[:error] = message('update_key.error_occured_while_renaming_key_of_x',
:params => [project.key, Api::Utils.exception_message(e, :backtrace => false)])
end
end
-
+
redirect_to :action => 'key', :id => project.root_project.id
end
-
+
def prepare_key_bulk_update
@project = get_current_project(params[:id])
-
+
@string_to_replace = params[:string_to_replace].strip
@replacement_string = params[:replacement_string].strip
if @string_to_replace.blank? || @replacement_string.blank?
def perform_key_bulk_update
project = get_current_project(params[:id])
-
+
string_to_replace = params[:string_to_replace].strip
replacement_string = params[:replacement_string].strip
-
+
unless string_to_replace.blank? || replacement_string.blank?
begin
java_facade.bulkUpdateKey(project.id, string_to_replace, replacement_string)
flash[:notice] = message('update_key.key_updated')
rescue Exception => e
- flash[:error] = message('update_key.error_occured_while_renaming_key_of_x',
+ flash[:error] = message('update_key.error_occured_while_renaming_key_of_x',
:params => [project.key, Api::Utils.exception_message(e, :backtrace => false)])
end
end
-
+
redirect_to :action => 'key', :id => project.id
end
redirect_to :action => 'links', :id => project.id
end
-
def settings
- @project = get_current_project(params[:id])
+ @resource = get_current_project(params[:id])
- @snapshot=@project.last_snapshot
- if !@project.project? && !@project.module?
+ @snapshot = @resource.last_snapshot
+ if !@resource.project? && !@resource.module?
redirect_to :action => 'index', :id => params[:id]
end
- @category=params[:category] ||= 'general'
- @definitions_per_category={}
- definitions = java_facade.getPropertyDefinitions()
- properties = definitions.getAll().select { |property| (@project.module? && property.isOnModule()) || (@project.project? && property.isOnProject()) }
- properties.each do |property|
- category = definitions.getCategory(property.getKey())
- @definitions_per_category[category]||=[]
- @definitions_per_category[category]<<property
+ if @resource.nil?
+ definitions_per_category = java_facade.propertyDefinitions.globalPropertiesByCategory
+ elsif @resource.project?
+ definitions_per_category = java_facade.propertyDefinitions.projectPropertiesByCategory
+ elsif @resource.module?
+ definitions_per_category = java_facade.propertyDefinitions.modulePropertiesByCategory
end
- end
+ @category = params[:category] || 'general'
+ @categories = definitions_per_category.keys
+ @definitions = definitions_per_category[@category] || []
+
+ not_found('category') unless @categories.include? @category
+ end
def events
@categories = EventCategory.categories(true)
@snapshot = Snapshot.find(params[:id])
@category = params[:category]
-
+
conditions = "resource_id=:resource_id"
values = {:resource_id => @snapshot.project_id}
unless @category.blank?
snapshots_to_be_deleted = Snapshot.find(:all, :conditions => ["status='U' AND project_id=?", @snapshot.project_id])
unless snapshots_to_be_deleted.empty?
conditions << " AND snapshot_id NOT IN (:sids)"
- values[:sids] = snapshots_to_be_deleted.map {|s| s.id}
+ values[:sids] = snapshots_to_be_deleted.map { |s| s.id }
end
category_names=@categories.map { |cat| cat.name }
snapshot=Snapshot.find(params[:sid])
not_found("Snapshot not found") unless snapshot
access_denied unless is_admin?(snapshot)
-
+
# We update all the related snapshots to have the same version as the next snapshot
next_snapshot = Snapshot.find(:first, :conditions => ['created_at>? and project_id=?', snapshot.created_at, snapshot.project_id], :order => 'created_at asc')
snapshots = find_project_snapshots(snapshot.id)
else
snapshots = find_project_snapshots(snapshot.id)
snapshots.each do |s|
- e = Event.new({:name => params[:event_name],
- :category => EventCategory::KEY_OTHER,
- :snapshot => s,
- :resource_id => s.project_id,
- :event_date => s.created_at})
+ e = Event.new({:name => params[:event_name],
+ :category => EventCategory::KEY_OTHER,
+ :snapshot => s,
+ :resource_id => s.project_id,
+ :event_date => s.created_at})
e.save!
end
flash[:notice] = message('project_history.event_created', :params => params[:event_name])
access_denied unless is_admin?(project)
project
end
-
+
def find_project_snapshots(root_snapshot_id)
snapshots = Snapshot.find(:all, :include => 'events', :conditions => ["(root_snapshot_id = ? OR id = ?) AND scope = 'PRJ'", root_snapshot_id, root_snapshot_id])
end
SECTION=Navigation::SECTION_CONFIGURATION
- SPECIAL_CATEGORIES=['email', 'encryption', 'server_id']
+ SPECIAL_CATEGORIES=%w(email encryption server_id)
- verify :method => :post, :only => ['update'], :redirect_to => {:action => :index}
+ verify :method => :post, :only => %w(update), :redirect_to => {:action => :index}
+ before_filter :admin_required, :only => %w(index)
def index
- access_denied unless is_admin?
- load_properties(nil)
- @category ||= 'general'
+ load_properties()
end
def update
- @project=nil
- resource_id=nil
- if params[:resource_id]
- @project=Project.by_key(params[:resource_id])
- access_denied unless (@project && is_admin?(@project))
- resource_id=@project.id
- else
- access_denied unless is_admin?
- end
- is_global=(@project.nil?)
-
- load_properties(@project)
-
- @persisted_properties_per_key={}
- if @category && @definitions_per_category[@category]
- @definitions_per_category[@category].each do |property|
- value=params[property.getKey()]
- persisted_property = Property.find(:first, :conditions => {:prop_key=> property.key(), :resource_id => resource_id, :user_id => nil})
-
- # update the property
- if persisted_property
- if value.empty?
- Property.delete_all('prop_key' => property.key(), 'resource_id' => resource_id, 'user_id' => nil)
- java_facade.setGlobalProperty(property.getKey(), nil) if is_global
- elsif persisted_property.text_value != value.to_s
- persisted_property.text_value = value.to_s
- if persisted_property.save && is_global
- java_facade.setGlobalProperty(property.getKey(), value.to_s)
- end
- @persisted_properties_per_key[persisted_property.key]=persisted_property
- end
-
- # create the property
- elsif value.present?
- persisted_property=Property.new(:prop_key => property.key(), :text_value => value.to_s, :resource_id => resource_id)
- if persisted_property.save && is_global
- java_facade.setGlobalProperty(property.getKey(), value.to_s)
- end
- @persisted_properties_per_key[persisted_property.key]=persisted_property
- end
- end
+ resource_id = params[:resource_id]
+ @resource = Project.by_key(resource_id) if resource_id
+
+ access_denied if (@resource && !is_admin?(@resource))
+ access_denied if (@resource.nil? && !is_admin?)
- params[:layout]='false'
- render :partial => 'settings/properties'
+ load_properties()
+
+ @definitions.map(&:key).each do |key|
+ value = params[key]
+
+ if value.blank?
+ Property.clear(key, resource_id)
+ else
+ Property.set(key, value, resource_id)
+ end
end
+
+ render :partial => 'settings/properties'
end
private
- def load_properties(project)
- @category=params[:category]
- @definitions_per_category={}
- definitions = java_facade.getPropertyDefinitions()
- definitions.getAll().select { |property_definition|
- (project.nil? && property_definition.isGlobal()) || (project && project.module? && property_definition.isOnModule()) || (project && project.project? && property_definition.isOnProject())
- }.each do |property_definition|
- category = definitions.getCategory(property_definition.getKey())
- @definitions_per_category[category]||=[]
- @definitions_per_category[category]<<property_definition
- end
+ def load_properties
+ @category = params[:category] || 'general'
- SPECIAL_CATEGORIES.each do |category|
- @definitions_per_category[category]=[]
+ if @resource.nil?
+ definitions_per_category = java_facade.propertyDefinitions.globalPropertiesByCategory
+ elsif @resource.project?
+ definitions_per_category = java_facade.propertyDefinitions.projectPropertiesByCategory
+ elsif @resource.module?
+ definitions_per_category = java_facade.propertyDefinitions.modulePropertiesByCategory
end
+
+ @categories = definitions_per_category.keys + SPECIAL_CATEGORIES
+ @definitions = definitions_per_category[@category] || []
+
+ not_found('category') unless @categories.include? @category
end
+
end
#
module ProjectHelper
include ActionView::Helpers::UrlHelper
+ include SettingsHelper
def formatted_value(measure, default='')
measure ? measure.formatted_value : default
--- /dev/null
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2012 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar 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.
+#
+# Sonar 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 Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+#
+module SettingsHelper
+ def category_name(category)
+ message_or_default("property.category.#{category}", category)
+ end
+
+ def property_name(property)
+ message_or_default("property.#{property.key()}.name", property.name())
+ end
+
+ def property_description(property)
+ message_or_default("property.#{property.key()}.description", property.description())
+ end
+
+ def property_value(property)
+ if property.multi_values
+ Property.values(property.key, @resource ? @resource.id : nil)
+ else
+ Property.value(property.key, @resource ? @resource.id : nil, '')
+ end
+ end
+
+ # for backward-compatibility with properties that do not define the type TEXT
+ def property_type(property, value)
+ if property.getType().to_s=='STRING' && value && value.include?('\n')
+ return 'TEXT'
+ end
+ property.getType()
+ end
+
+ def message_or_default(message_key, default)
+ message(message_key, :default => default)
+ end
+
+ def by_name(categories)
+ categories.sort_by { |category| category_name(category) }
+ end
+end
class Property < ActiveRecord::Base
validates_presence_of :prop_key
+ named_scope :with_key, lambda { |value| {:conditions => {:prop_key, value}} }
+ named_scope :with_resource, lambda { |value| {:conditions => {:resource_id => value}} }
+ named_scope :with_user, lambda { |value| {:conditions => {:user_id => value}} }
+
def key
prop_key
end
text_value
end
- def self.hash(resource_id=nil)
- hash={}
- Property.find(:all, :conditions => {'resource_id' => resource_id, 'user_id' => nil}).each do |prop|
- hash[prop.key]=prop.value
- end
- hash
+ def self.hash(resource_id=nil, user_id=nil)
+ properties = Property.with_resource(resource_id).with_user(user_id)
+
+ Hash[properties.map { |prop| [prop.key, prop.value] }]
end
- def self.value(key, resource_id=nil, default_value=nil)
- prop=Property.find(:first, :conditions => {'prop_key' => key, 'resource_id' => resource_id, 'user_id' => nil})
- if prop
- prop.text_value || default_value
- else
- default_value
- end
+ def self.clear(key, resource_id=nil, user_id=nil)
+ all(key, resource_id, user_id).delete_all
+ Java::OrgSonarServerUi::JRubyFacade.getInstance().setGlobalProperty(key, nil) unless resource_id
+ end
+
+ def self.by_key(key, resource_id=nil, user_id=nil)
+ all(key, resource_id, user_id).first
+ end
+
+ def self.by_key_prefix(prefix)
+ Property.find(:all, :conditions => ['prop_key like ?', prefix + '%'])
+ end
+
+ def self.value(key, resource_id=nil, default_value=nil, user_id=nil)
+ property = by_key(key, resource_id, user_id)
+ return default_value unless property
+
+ property.text_value || default_value
end
- def self.values(key, resource_id=nil)
- Property.find(:all, :conditions => {'prop_key' => key, 'resource_id' => resource_id, 'user_id' => nil}).collect { |p| p.text_value }
+ def self.values(key, resource_id=nil, user_id=nil)
+ value = value(key, resource_id, '', user_id)
+ values = value.split(',')
+ values.empty? ? [nil] : values.map { |v| v.gsub('%2C', ',') }
end
- def self.set(key, value, resource_id=nil)
+ def self.set(key, value, resource_id=nil, user_id=nil)
+ if value.kind_of? Array
+ value = value.map { |v| v.gsub(',', '%2C') }.join(',')
+ end
+
text_value = (value.nil? ? nil : value.to_s)
- prop = Property.new(:prop_key => key, :text_value => text_value, :resource_id => resource_id)
+ prop = Property.new(:prop_key => key, :text_value => text_value, :resource_id => resource_id, :user_id => user_id)
if prop.valid?
Property.transaction do
- Property.delete_all('prop_key' => key, 'resource_id' => resource_id, 'user_id' => nil)
+ Property.delete_all(:prop_key => key, :resource_id => resource_id, :user_id => user_id)
prop.save
end
Java::OrgSonarServerUi::JRubyFacade.getInstance().setGlobalProperty(key, text_value) unless resource_id
prop
end
- def self.clear(key, resource_id=nil)
- Property.delete_all('prop_key' => key, 'resource_id' => resource_id, 'user_id' => nil)
- Java::OrgSonarServerUi::JRubyFacade.getInstance().setGlobalProperty(key, nil) unless resource_id
- end
-
- def self.by_key(key, resource_id=nil)
- Property.find(:first, :conditions => {'prop_key' => key, 'resource_id' => resource_id, 'user_id' => nil})
- end
-
- def self.by_key_prefix(prefix)
- Property.find(:all, :conditions => ["prop_key like ?", prefix + '%'])
- end
-
- def self.update(key, value)
- property = Property.find(:first, :conditions => {:prop_key => key, :resource_id => nil, :user_id => nil});
- property.text_value = value
- property.save
- Java::OrgSonarServerUi::JRubyFacade.getInstance().setGlobalProperty(key, value)
- property
+ def self.update(key, value, resource_id=nil, user_id=nil)
+ set(key, value, resource_id, user_id)
end
def to_hash_json
private
+ def self.all(key, resource_id=nil, user_id=nil)
+ Property.with_key(key).with_resource(resource_id).with_user(user_id)
+ end
+
def validate
if java_definition
validation_result=java_definition.validate(text_value)
--- /dev/null
+<div class="multi_value">
+ <%= render "settings/type_#{property_type(property, value)}", :property => property, :value => value -%>
+ <a href="#" class="delete"></a>
+ <br/>
+</div>
-<% if @category && @definitions_per_category[@category]
- category_name = message("property.category.#{@category}", :default => @category)
- if SettingsController::SPECIAL_CATEGORIES.include?(@category)
-%>
- <%= render :partial => 'special', :locals => {:url => url_for(:controller => "#{@category}_configuration")} -%>
- <%
- elsif !@definitions_per_category[@category].empty?
- %>
- <% form_remote_tag :url => {:controller => 'settings', :action => 'update', :category => @category, :resource_id => @project ? @project.id : nil},
- :method => :post,
- :before => "$('submit_settings').hide();$('loading_settings').show()",
- :update => 'properties' do -%>
+<% if SettingsController::SPECIAL_CATEGORIES.include?(@category) -%>
+ <%= render 'special', :url => url_for(:controller => "#{@category}_configuration") -%>
+<% else -%>
+ <% form_remote_tag :url => {:controller => 'settings', :action => 'update', :category => @category, :resource_id => @resource ? @resource.id : nil},
+ :method => :post,
+ :before => "$('submit_settings').hide();$('loading_settings').show();",
+ :update => 'properties',
+ :script => false do -%>
- <table class="data marginbottom10" style="margin: 10px">
- <thead>
- <tr>
- <th>
- <span><%= h(category_name) -%></span>
- </th>
- </tr>
- </thead>
- <tbody>
- <%
- if @definitions_per_category[@category]
- @definitions_per_category[@category].each do |property|
- value = nil
-
- # set when form has been submitted but some errors have been raised
- if @persisted_properties_per_key
- p = @persisted_properties_per_key[property.key]
- if p
- value = p.text_value
- end
- end
+ <table class="data marginbottom10" style="margin: 10px;">
+ <thead>
+ <tr>
+ <th><%= h category_name(@category) -%></th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @definitions.each do |property| -%>
+ <tr class="<%= cycle('even', 'odd', :name => 'properties') -%>">
+ <td style="padding: 10px" id="block_<%= property.key -%>">
+ <h3>
+ <div><%= property_name(property) -%></div>
+ <div class="note"><%= property.key -%></div>
+ </h3>
+ <% desc=property_description(property) -%>
+ <% unless desc.blank? %>
+ <p class="marginbottom10"><%= desc -%></p>
+ <% end -%>
+ <% value = property_value(property) -%>
+ <% if property.multi_values -%>
+ <% value.each do |sub_value| -%>
+ <%= render "settings/multi_value", :property => property, :value => sub_value -%>
+ <% end -%>
+ <div class="template" style="display:none;">
+ <%= render "settings/multi_value", :property => property, :value => nil -%>
+ </div>
+ <a href="#" class="add"><%= message('settings.add') -%></a>
+ <% else -%>
+ <%= render "settings/type_#{property_type(property, value)}", :property => property, :value => value -%>
+ <% end -%>
- # if fresh form or no error, get the current value
- value = Property.value(property.getKey(), (@project ? @project.id : nil), '') unless value
+ <% default_prop_value = (@resource ? Property.value(property.key, nil, property.defaultValue) : property.defaultValue) -%>
+ <% unless default_prop_value.blank? %>
+ <p class="note">Default: <%= property.type.to_s=='PASSWORD' ? '********' : h(default_prop_value) -%></p>
+ <% end -%>
+ </td>
+ </tr>
+ <% end -%>
+ </tbody>
+ </table>
+ <div style="padding-left: 16px;">
+ <%= submit_tag(message('settings.save_category', :params => [category_name(@category)]), :id => 'submit_settings') -%>
+ <img src="<%= ApplicationController.root_context -%>/images/loading.gif" id="loading_settings" style="display:none;">
+ </div>
+ <% end -%>
+<% end -%>
- # for backward-compatibility with properties that do not define the type TEXT
- property_type = property.getType()
- if property_type.to_s=='STRING' && value.include?("\n")
- property_type = 'TEXT'
- end
+<script type="text/javascript">
+ $j('.delete').live('click', function () {
+ $j(this).parent('.multi_value').remove();
+ });
- %>
- <tr class="<%= cycle('even', 'odd', :name => 'properties') -%>">
- <td style="padding: 10px" id="block_<%= property.getKey() -%>">
- <h3>
- <%= message("property.#{property.key()}.name", :default => property.name()) -%>
- <br/><span class="note"><%= property.getKey() -%></span>
- </h3>
- <%
- desc=message("property.#{property.key()}.description", :default => property.description())
- if desc.present? %>
- <p class="marginbottom10"><%= desc -%></p>
- <% end %>
- <div><%= render :partial => "settings/type_#{property_type}", :locals => {:property => property, :value => value} -%></div>
- <%
- if p && !p.valid?
- %>
- <div class="error"><%= p.validation_error_message -%></div>
- <%
- end
- %>
- <p>
- <%
- default_prop_value = (@project ? Property.value(property.key(), nil, property.defaultValue()) : property.defaultValue())
- unless default_prop_value.blank? %>
- <span class="note">Default : <%= property.getType().to_s=='PASSWORD' ? '********' : h(default_prop_value) -%></span>
- <% end %>
- </p>
- </td>
- </tr>
- <% end
- end
- %>
- </tbody>
- </table>
- <div style="padding-left: 16px">
- <%= submit_tag(message('settings.save_category', :params => [category_name]), :id => 'submit_settings') -%>
- <img src="<%= ApplicationController.root_context -%>/images/loading.gif" id="loading_settings" style="display:none">
- </div>
- <% end %>
- <% end
- end
- %>
+ $j('.add').live('click', function () {
+ var template = $j(this).siblings('.template');
+ template.before(template.html());
+ });
+</script>
\ No newline at end of file
-<style type="text/css">
- #plugins .plugin {
- padding: 5px;
- border: 1px solid #ddd;
- background-color: #fff;
- }
+<h1 class="marginbottom10"><%= message('settings.page') -%></h1>
- #plugins .plugin h2 {
- margin-left: 10px;
- font-size: 122%;
- color: #333;
- }
+<table width="100%">
+ <tr>
+ <td width="1%" nowrap class="column first">
+ <table class="data selector">
+ <thead>
+ <tr>
+ <th><%= message('category') -%></th>
+ </tr>
+ </thead>
+ <tbody>
+ <% by_name(@categories).each do |category| -%>
+ <tr id="select_<%= category -%>" class="select <%= cycle('even', 'odd', :name => 'category') -%> <%= 'selected' if @category==category -%>">
+ <td><%= link_to category_name(category), :category => category -%></td>
+ </tr>
+ <% end -%>
+ </tbody>
+ </table>
+ <br/>
+ </td>
- #plugins .plugin h3 {
- margin-left: 5px;
- }
+ <td class="column">
+ <div id="properties">
+ <%= render 'settings/properties' -%>
+ </div>
+ </td>
+ </tr>
+</table>
- #plugins .plugin p {
- padding: 5px 5px;
- }
-
- #plugins .plugin img {
- padding: 5px 0 0 5px;
- }
-</style>
<script type="text/javascript">
function enlargeTextInput(propertyKey) {
var eltId = 'input_' + propertyKey;
$(eltId).parentNode.replace(textArea);
}
</script>
-<div id="plugins">
- <h1 class="marginbottom10"><%= message('settings.page') -%></h1>
- <table width="100%">
- <tr>
- <td width="1%" nowrap class="column first">
- <table class="data selector">
- <thead>
- <tr>
- <th>
- <span>Category</span>
- </th>
- </tr>
- </thead>
- <tbody>
- <%
- @definitions_per_category.keys.sort_by { |category| message("property.category.#{category}", :default => category).upcase }.each do |category|
- if !@definitions_per_category[category].empty? || SettingsController::SPECIAL_CATEGORIES.include?(category)
- %>
- <tr class="select <%= cycle('even', 'odd', :name => 'category') -%> <%= 'selected' if @category==category -%>" id="select_<%= category -%>">
- <td><%= link_to message("property.category.#{category}", :default => category), :overwrite_params => {:category => category} -%></td>
- </tr>
- <% end
- end
- %>
- </tbody>
- </table>
- <br/>
- </td>
-
- <td class="column">
- <div id="properties" style="width:99%">
- <%= render :partial => 'settings/properties' -%>
- </div>
- </td>
- </tr>
- </table>
-</div>
-<input type="text" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey() -%>"/>
-<%= link_to_function(image_tag('zoom.png'), "enlargeTextInput('#{property.getKey()}')", :class => 'nolink') -%>
\ No newline at end of file
+<input type="text" name="<%= h property.key -%>[]" value="<%= h value if value -%>" size="50" id="input_<%= h property.key -%>"/>
+<%= link_to_function(image_tag('zoom.png'), "enlargeTextInput('#{property.key}')", :class => 'nolink') -%>
\ No newline at end of file
-<%= render :partial => 'settings', :locals => {:project=>nil} %>
\ No newline at end of file
+<%= render 'settings', :project => nil %>
\ No newline at end of file
padding: 2px 0 2px 20px;
}
+.delete {
+ background: url("../images/cross.png") no-repeat scroll left 50% transparent;
+ padding: 2px 0 2px 20px;
+}
+
.restore {
display: block;
background: url("../images/restore.gif") no-repeat scroll left 50% transparent;