@@ -116,389 +116,389 @@ import org.sonar.plugins.core.widgets.reviews.UnplannedReviewsWidget; | |||
import java.util.List; | |||
@Properties({ | |||
@Property( | |||
key = CoreProperties.SERVER_BASE_URL, | |||
defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE, | |||
name = "Server base URL", | |||
description = "HTTP URL of this Sonar server, such as <i>http://yourhost.yourdomain/sonar</i>. This value is used i.e. to create links in emails.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_HOME_PAGE, | |||
defaultValue = "", | |||
name = "Project Home Page", | |||
description = "HTTP URL of the home page of the project.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_CI, | |||
defaultValue = "", | |||
name = "CI server", | |||
description = "HTTP URL of the continuous integration server.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_ISSUE_TRACKER, | |||
defaultValue = "", | |||
name = "Issue Tracker", | |||
description = "HTTP URL of the issue tracker.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_SOURCES, | |||
defaultValue = "", | |||
name = "SCM server", | |||
description = "HTTP URL of the server which hosts the sources of the project.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_SOURCES_DEV, | |||
defaultValue = "", | |||
name = "SCM connection for developers", | |||
description = "HTTP URL used by developers to connect to the SCM server for the project.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.PROJECT_LANGUAGE_PROPERTY, | |||
defaultValue = Java.KEY, | |||
name = "Default language", | |||
description = "Default language of the source code to analyse.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.GLOBAL_EXCLUSIONS_PROPERTY, | |||
name = "Global source exclusions", | |||
description = "Exclude sources from code analysis. Applies to every project. Cannot be overriden. Changes will be applied during next code analysis.", | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS), | |||
@Property( | |||
key = CoreProperties.GLOBAL_TEST_EXCLUSIONS_PROPERTY, | |||
name = "Global test exclusions", | |||
description = "Exclude tests from code analysis. Applies to every project. Cannot be overriden. Changes will be applied during next code analysis.", | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS, | |||
defaultValue = CoreProperties.GLOBAL_TEST_EXCLUSIONS_DEFAULT), | |||
@Property( | |||
key = CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, | |||
name = "Exclusions", | |||
description = "Exclude sources from code analysis. Changes will be applied during next code analysis.", | |||
project = true, | |||
global = true, | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS), | |||
@Property( | |||
key = CoreProperties.PROJECT_TEST_EXCLUSIONS_PROPERTY, | |||
name = "Test Exclusions", | |||
description = "Exclude tests from code analysis. Changes will be applied during next code analysis.", | |||
project = true, | |||
global = true, | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS), | |||
@Property( | |||
key = CoreProperties.CORE_IMPORT_SOURCES_PROPERTY, | |||
defaultValue = "" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE, | |||
name = "Import sources", | |||
description = "Set to false if sources should not be displayed, e.g. for security reasons.", | |||
project = true, | |||
module = true, | |||
global = true, | |||
category = CoreProperties.CATEGORY_SECURITY, | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY, | |||
defaultValue = "" + CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE, | |||
name = "Tendency period", | |||
description = TendencyDecorator.PROP_DAYS_DESCRIPTION, | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS, | |||
type = PropertyType.INTEGER), | |||
@Property( | |||
key = CoreProperties.SKIP_TENDENCIES_PROPERTY, | |||
defaultValue = "" + CoreProperties.SKIP_TENDENCIES_DEFAULT_VALUE, | |||
name = "Skip tendencies", | |||
description = "Skip calculation of measure tendencies", | |||
project = true, | |||
module = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS, | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_SKIPPED_MODULES_PROPERTY, | |||
name = "Exclude modules", | |||
description = "Maven artifact ids of modules to exclude.", | |||
project = true, | |||
global = false, | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS), | |||
@Property( | |||
key = CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, | |||
defaultValue = "" + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE, | |||
name = "Force user authentication", | |||
description = "Forcing user authentication stops un-logged users to access Sonar.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_SECURITY, | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY, | |||
defaultValue = "" + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE, | |||
name = "Allow users to sign up online", | |||
description = "Users can sign up online.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_SECURITY, | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_DEFAULT_GROUP, | |||
defaultValue = CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE, | |||
name = "Default user group", | |||
description = "Any new users will automatically join this group.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_SECURITY), | |||
@Property( | |||
key = CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY, | |||
defaultValue = "en", | |||
name = "Locale used for violation messages", | |||
description = "Locale to be used when generating violation messages. It's up to each rule engine to support this global internationalization property", | |||
project = true, | |||
global = true, | |||
category = CoreProperties.CATEGORY_L10N), | |||
@Property( | |||
key = "sonar.timemachine.period1", | |||
name = "Period 1", | |||
description = "Period used to compare measures and track new violations. Values are : <ul class='bullet'><li>Number of days before " + | |||
"analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_analysis' to " + | |||
"compare to previous analysis</li><li>'previous_version' to compare to the previous version in the project history</li></ul>" + | |||
"<p>When specifying a number of days or a date, the snapshot selected for comparison is " + | |||
" the first one available inside the corresponding time range. </p>" + | |||
"<p>Changing this property only takes effect after subsequent project inspections.<p/>", | |||
project = false, | |||
global = true, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = "sonar.timemachine.period2", | |||
name = "Period 2", | |||
description = "See the property 'Period 1'", | |||
project = false, | |||
global = true, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = "sonar.timemachine.period3", | |||
name = "Period 3", | |||
description = "See the property 'Period 1'", | |||
project = false, | |||
global = true, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = "sonar.timemachine.period4", | |||
name = "Period 4", | |||
description = "Period used to compare measures and track new violations. This property is specific to the project. Values are : " + | |||
"<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, " + | |||
"for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li>" + | |||
"<li>'previous_version' to compare to the previous version in the project history</li><li>A version, for example 1.2</li></ul>" + | |||
"<p>When specifying a number of days or a date, the snapshot selected for comparison is the first one available inside the corresponding time range. </p>" + | |||
"<p>Changing this property only takes effect after subsequent project inspections.<p/>", | |||
project = true, | |||
global = false, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = "sonar.timemachine.period5", | |||
name = "Period 5", | |||
description = "See the property 'Period 4'", | |||
project = true, | |||
global = false, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = CoreProperties.DRY_RUN, | |||
defaultValue = "false", | |||
name = "Dry Run", | |||
type = PropertyType.BOOLEAN, | |||
global = false, project = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.DRY_RUN_INCLUDE_PLUGINS, | |||
name = "Plugins accepted for dry run", | |||
defaultValue = CoreProperties.DRY_RUN_INCLUDE_PLUGINS_DEFAULT_VALUE, | |||
global = true, project = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, | |||
name = "Plugins excluded for dry run", | |||
global = true, project = false, | |||
defaultValue = CoreProperties.DRY_RUN_EXCLUDE_PLUGINS_DEFAULT_VALUE, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = "sonar.dryRun.export.path", | |||
defaultValue = "dryRun.json", | |||
name = "Dry Run Results Export File", | |||
type = PropertyType.STRING, | |||
global = false, project = false), | |||
@Property( | |||
key = CoreProperties.SERVER_BASE_URL, | |||
defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE, | |||
name = "Server base URL", | |||
description = "HTTP URL of this Sonar server, such as <i>http://yourhost.yourdomain/sonar</i>. This value is used i.e. to create links in emails.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_HOME_PAGE, | |||
defaultValue = "", | |||
name = "Project Home Page", | |||
description = "HTTP URL of the home page of the project.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_CI, | |||
defaultValue = "", | |||
name = "CI server", | |||
description = "HTTP URL of the continuous integration server.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_ISSUE_TRACKER, | |||
defaultValue = "", | |||
name = "Issue Tracker", | |||
description = "HTTP URL of the issue tracker.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_SOURCES, | |||
defaultValue = "", | |||
name = "SCM server", | |||
description = "HTTP URL of the server which hosts the sources of the project.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.LINKS_SOURCES_DEV, | |||
defaultValue = "", | |||
name = "SCM connection for developers", | |||
description = "HTTP URL used by developers to connect to the SCM server for the project.", | |||
project = false, | |||
global = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.PROJECT_LANGUAGE_PROPERTY, | |||
defaultValue = Java.KEY, | |||
name = "Default language", | |||
description = "Default language of the source code to analyse.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.GLOBAL_EXCLUSIONS_PROPERTY, | |||
name = "Global source exclusions", | |||
description = "Exclude sources from code analysis. Applies to every project. Cannot be overriden. Changes will be applied during next code analysis.", | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS), | |||
@Property( | |||
key = CoreProperties.GLOBAL_TEST_EXCLUSIONS_PROPERTY, | |||
name = "Global test exclusions", | |||
description = "Exclude tests from code analysis. Applies to every project. Cannot be overriden. Changes will be applied during next code analysis.", | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS, | |||
defaultValue = CoreProperties.GLOBAL_TEST_EXCLUSIONS_DEFAULT), | |||
@Property( | |||
key = CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, | |||
name = "Exclusions", | |||
description = "Exclude sources from code analysis. Changes will be applied during next code analysis.", | |||
project = true, | |||
global = true, | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS), | |||
@Property( | |||
key = CoreProperties.PROJECT_TEST_EXCLUSIONS_PROPERTY, | |||
name = "Test Exclusions", | |||
description = "Exclude tests from code analysis. Changes will be applied during next code analysis.", | |||
project = true, | |||
global = true, | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS), | |||
@Property( | |||
key = CoreProperties.CORE_IMPORT_SOURCES_PROPERTY, | |||
defaultValue = "" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE, | |||
name = "Import sources", | |||
description = "Set to false if sources should not be displayed, e.g. for security reasons.", | |||
project = true, | |||
module = true, | |||
global = true, | |||
category = CoreProperties.CATEGORY_SECURITY, | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY, | |||
defaultValue = "" + CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE, | |||
name = "Tendency period", | |||
description = TendencyDecorator.PROP_DAYS_DESCRIPTION, | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS, | |||
type = PropertyType.INTEGER), | |||
@Property( | |||
key = CoreProperties.SKIP_TENDENCIES_PROPERTY, | |||
defaultValue = "" + CoreProperties.SKIP_TENDENCIES_DEFAULT_VALUE, | |||
name = "Skip tendencies", | |||
description = "Skip calculation of measure tendencies", | |||
project = true, | |||
module = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS, | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_SKIPPED_MODULES_PROPERTY, | |||
name = "Exclude modules", | |||
description = "Maven artifact ids of modules to exclude.", | |||
project = true, | |||
global = false, | |||
multiValues = true, | |||
category = CoreProperties.CATEGORY_EXCLUSIONS), | |||
@Property( | |||
key = CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, | |||
defaultValue = "" + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE, | |||
name = "Force user authentication", | |||
description = "Forcing user authentication stops un-logged users to access Sonar.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_SECURITY, | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY, | |||
defaultValue = "" + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE, | |||
name = "Allow users to sign up online", | |||
description = "Users can sign up online.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_SECURITY, | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_DEFAULT_GROUP, | |||
defaultValue = CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE, | |||
name = "Default user group", | |||
description = "Any new users will automatically join this group.", | |||
project = false, | |||
global = true, | |||
category = CoreProperties.CATEGORY_SECURITY), | |||
@Property( | |||
key = CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY, | |||
defaultValue = "en", | |||
name = "Locale used for violation messages", | |||
description = "Locale to be used when generating violation messages. It's up to each rule engine to support this global internationalization property", | |||
project = true, | |||
global = true, | |||
category = CoreProperties.CATEGORY_L10N), | |||
@Property( | |||
key = "sonar.timemachine.period1", | |||
name = "Period 1", | |||
description = "Period used to compare measures and track new violations. Values are : <ul class='bullet'><li>Number of days before " + | |||
"analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_analysis' to " + | |||
"compare to previous analysis</li><li>'previous_version' to compare to the previous version in the project history</li></ul>" + | |||
"<p>When specifying a number of days or a date, the snapshot selected for comparison is " + | |||
" the first one available inside the corresponding time range. </p>" + | |||
"<p>Changing this property only takes effect after subsequent project inspections.<p/>", | |||
project = false, | |||
global = true, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = "sonar.timemachine.period2", | |||
name = "Period 2", | |||
description = "See the property 'Period 1'", | |||
project = false, | |||
global = true, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = "sonar.timemachine.period3", | |||
name = "Period 3", | |||
description = "See the property 'Period 1'", | |||
project = false, | |||
global = true, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = "sonar.timemachine.period4", | |||
name = "Period 4", | |||
description = "Period used to compare measures and track new violations. This property is specific to the project. Values are : " + | |||
"<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, " + | |||
"for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li>" + | |||
"<li>'previous_version' to compare to the previous version in the project history</li><li>A version, for example 1.2</li></ul>" + | |||
"<p>When specifying a number of days or a date, the snapshot selected for comparison is the first one available inside the corresponding time range. </p>" + | |||
"<p>Changing this property only takes effect after subsequent project inspections.<p/>", | |||
project = true, | |||
global = false, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = "sonar.timemachine.period5", | |||
name = "Period 5", | |||
description = "See the property 'Period 4'", | |||
project = true, | |||
global = false, | |||
defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5, | |||
category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), | |||
@Property( | |||
key = CoreProperties.DRY_RUN, | |||
defaultValue = "false", | |||
name = "Dry Run", | |||
type = PropertyType.BOOLEAN, | |||
global = false, project = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.DRY_RUN_INCLUDE_PLUGINS, | |||
name = "Plugins accepted for dry run", | |||
defaultValue = CoreProperties.DRY_RUN_INCLUDE_PLUGINS_DEFAULT_VALUE, | |||
global = true, project = false, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, | |||
name = "Plugins excluded for dry run", | |||
global = true, project = false, | |||
defaultValue = CoreProperties.DRY_RUN_EXCLUDE_PLUGINS_DEFAULT_VALUE, | |||
category = CoreProperties.CATEGORY_GENERAL), | |||
@Property( | |||
key = "sonar.dryRun.export.path", | |||
defaultValue = "dryRun.json", | |||
name = "Dry Run Results Export File", | |||
type = PropertyType.STRING, | |||
global = false, project = false), | |||
// SERVER-SIDE TECHNICAL PROPERTIES | |||
// SERVER-SIDE TECHNICAL PROPERTIES | |||
@Property( | |||
key = "sonar.security.realm", | |||
name = "Security Realm", | |||
project = false, | |||
global = false | |||
), | |||
@Property( | |||
key = "sonar.security.savePassword", | |||
name = "Save external password", | |||
project = false, | |||
global = false | |||
), | |||
@Property( | |||
key = "sonar.authenticator.downcase", | |||
name = "Downcase login", | |||
description = "Downcase login during user authentication, typically for Active Directory", | |||
project = false, | |||
global = false, | |||
defaultValue = "false", | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS, | |||
name = "Create user accounts", | |||
description = "Create accounts when authenticating users via an external system", | |||
project = false, | |||
global = false, | |||
defaultValue = "true", | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE, | |||
name = "Ignore failures during authenticator startup", | |||
defaultValue = "false", | |||
project = false, | |||
global = false, | |||
type = PropertyType.BOOLEAN) | |||
@Property( | |||
key = "sonar.security.realm", | |||
name = "Security Realm", | |||
project = false, | |||
global = false | |||
), | |||
@Property( | |||
key = "sonar.security.savePassword", | |||
name = "Save external password", | |||
project = false, | |||
global = false | |||
), | |||
@Property( | |||
key = "sonar.authenticator.downcase", | |||
name = "Downcase login", | |||
description = "Downcase login during user authentication, typically for Active Directory", | |||
project = false, | |||
global = false, | |||
defaultValue = "false", | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS, | |||
name = "Create user accounts", | |||
description = "Create accounts when authenticating users via an external system", | |||
project = false, | |||
global = false, | |||
defaultValue = "true", | |||
type = PropertyType.BOOLEAN), | |||
@Property( | |||
key = CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE, | |||
name = "Ignore failures during authenticator startup", | |||
defaultValue = "false", | |||
project = false, | |||
global = false, | |||
type = PropertyType.BOOLEAN) | |||
}) | |||
public final class CorePlugin extends SonarPlugin { | |||
@SuppressWarnings("unchecked") | |||
public List getExtensions() { | |||
return ImmutableList.of( | |||
DefaultResourceTypes.class, | |||
UserManagedMetrics.class, | |||
ProjectFileSystemLogger.class, | |||
Periods.class, | |||
DefaultResourceTypes.class, | |||
UserManagedMetrics.class, | |||
ProjectFileSystemLogger.class, | |||
Periods.class, | |||
// maven | |||
MavenInitializer.class, | |||
// maven | |||
MavenInitializer.class, | |||
// languages | |||
Java.class, | |||
// languages | |||
Java.class, | |||
// pages | |||
Lcom4Viewer.class, | |||
TestsViewer.class, | |||
// pages | |||
Lcom4Viewer.class, | |||
TestsViewer.class, | |||
// measure filters | |||
ProjectFilter.class, | |||
MyFavouritesFilter.class, | |||
// measure filters | |||
ProjectFilter.class, | |||
MyFavouritesFilter.class, | |||
// widgets | |||
AlertsWidget.class, | |||
CoverageWidget.class, | |||
ItCoverageWidget.class, | |||
CommentsDuplicationsWidget.class, | |||
DescriptionWidget.class, | |||
ComplexityWidget.class, | |||
RulesWidget.class, | |||
SizeWidget.class, | |||
EventsWidget.class, | |||
CustomMeasuresWidget.class, | |||
TimelineWidget.class, | |||
TimeMachineWidget.class, | |||
HotspotMetricWidget.class, | |||
HotspotMostViolatedResourcesWidget.class, | |||
HotspotMostViolatedRulesWidget.class, | |||
MyReviewsWidget.class, | |||
ProjectReviewsWidget.class, | |||
FalsePositiveReviewsWidget.class, | |||
ReviewsPerDeveloperWidget.class, | |||
PlannedReviewsWidget.class, | |||
UnplannedReviewsWidget.class, | |||
ActionPlansWidget.class, | |||
ReviewsMetricsWidget.class, | |||
TreemapWidget.class, | |||
MeasureFilterListWidget.class, | |||
MeasureFilterTreemapWidget.class, | |||
WelcomeWidget.class, | |||
// widgets | |||
AlertsWidget.class, | |||
CoverageWidget.class, | |||
ItCoverageWidget.class, | |||
CommentsDuplicationsWidget.class, | |||
DescriptionWidget.class, | |||
ComplexityWidget.class, | |||
RulesWidget.class, | |||
SizeWidget.class, | |||
EventsWidget.class, | |||
CustomMeasuresWidget.class, | |||
TimelineWidget.class, | |||
TimeMachineWidget.class, | |||
HotspotMetricWidget.class, | |||
HotspotMostViolatedResourcesWidget.class, | |||
HotspotMostViolatedRulesWidget.class, | |||
MyReviewsWidget.class, | |||
ProjectReviewsWidget.class, | |||
FalsePositiveReviewsWidget.class, | |||
ReviewsPerDeveloperWidget.class, | |||
PlannedReviewsWidget.class, | |||
UnplannedReviewsWidget.class, | |||
ActionPlansWidget.class, | |||
ReviewsMetricsWidget.class, | |||
TreemapWidget.class, | |||
MeasureFilterListWidget.class, | |||
MeasureFilterTreemapWidget.class, | |||
WelcomeWidget.class, | |||
// dashboards | |||
ProjectDefaultDashboard.class, | |||
ProjectHotspotDashboard.class, | |||
ProjectReviewsDashboard.class, | |||
ProjectTimeMachineDashboard.class, | |||
GlobalDefaultDashboard.class, | |||
// dashboards | |||
ProjectDefaultDashboard.class, | |||
ProjectHotspotDashboard.class, | |||
ProjectReviewsDashboard.class, | |||
ProjectTimeMachineDashboard.class, | |||
GlobalDefaultDashboard.class, | |||
// chart | |||
XradarChart.class, | |||
DistributionBarChart.class, | |||
DistributionAreaChart.class, | |||
// chart | |||
XradarChart.class, | |||
DistributionBarChart.class, | |||
DistributionAreaChart.class, | |||
// colorizers | |||
JavaColorizerFormat.class, | |||
// colorizers | |||
JavaColorizerFormat.class, | |||
// batch | |||
ProfileSensor.class, | |||
ProfileEventsSensor.class, | |||
ProjectLinksSensor.class, | |||
UnitTestDecorator.class, | |||
VersionEventsSensor.class, | |||
CheckAlertThresholds.class, | |||
GenerateAlertEvents.class, | |||
ViolationsDecorator.class, | |||
WeightedViolationsDecorator.class, | |||
ViolationsDensityDecorator.class, | |||
LineCoverageDecorator.class, | |||
CoverageDecorator.class, | |||
BranchCoverageDecorator.class, | |||
ItLineCoverageDecorator.class, | |||
ItCoverageDecorator.class, | |||
ItBranchCoverageDecorator.class, | |||
OverallLineCoverageDecorator.class, | |||
OverallCoverageDecorator.class, | |||
OverallBranchCoverageDecorator.class, | |||
ApplyProjectRolesDecorator.class, | |||
ExcludedResourceFilter.class, | |||
CommentDensityDecorator.class, | |||
NoSonarFilter.class, | |||
DirectoriesDecorator.class, | |||
FilesDecorator.class, | |||
ReviewNotifications.class, | |||
ReviewWorkflowDecorator.class, | |||
ReferenceAnalysis.class, | |||
ManualMeasureDecorator.class, | |||
ManualViolationInjector.class, | |||
ViolationSeverityUpdater.class, | |||
IndexProjectPostJob.class, | |||
ReviewsMeasuresDecorator.class, | |||
// batch | |||
ProfileSensor.class, | |||
ProfileEventsSensor.class, | |||
ProjectLinksSensor.class, | |||
UnitTestDecorator.class, | |||
VersionEventsSensor.class, | |||
CheckAlertThresholds.class, | |||
GenerateAlertEvents.class, | |||
ViolationsDecorator.class, | |||
WeightedViolationsDecorator.class, | |||
ViolationsDensityDecorator.class, | |||
LineCoverageDecorator.class, | |||
CoverageDecorator.class, | |||
BranchCoverageDecorator.class, | |||
ItLineCoverageDecorator.class, | |||
ItCoverageDecorator.class, | |||
ItBranchCoverageDecorator.class, | |||
OverallLineCoverageDecorator.class, | |||
OverallCoverageDecorator.class, | |||
OverallBranchCoverageDecorator.class, | |||
ApplyProjectRolesDecorator.class, | |||
ExcludedResourceFilter.class, | |||
CommentDensityDecorator.class, | |||
NoSonarFilter.class, | |||
DirectoriesDecorator.class, | |||
FilesDecorator.class, | |||
ReviewNotifications.class, | |||
ReviewWorkflowDecorator.class, | |||
ReferenceAnalysis.class, | |||
ManualMeasureDecorator.class, | |||
ManualViolationInjector.class, | |||
ViolationSeverityUpdater.class, | |||
IndexProjectPostJob.class, | |||
ReviewsMeasuresDecorator.class, | |||
// time machine | |||
TendencyDecorator.class, | |||
VariationDecorator.class, | |||
ViolationTrackingDecorator.class, | |||
ViolationPersisterDecorator.class, | |||
NewViolationsDecorator.class, | |||
TimeMachineConfigurationPersister.class, | |||
NewCoverageFileAnalyzer.class, | |||
NewItCoverageFileAnalyzer.class, | |||
NewOverallCoverageFileAnalyzer.class, | |||
NewCoverageAggregator.class); | |||
// time machine | |||
TendencyDecorator.class, | |||
VariationDecorator.class, | |||
ViolationTrackingDecorator.class, | |||
ViolationPersisterDecorator.class, | |||
NewViolationsDecorator.class, | |||
TimeMachineConfigurationPersister.class, | |||
NewCoverageFileAnalyzer.class, | |||
NewItCoverageFileAnalyzer.class, | |||
NewOverallCoverageFileAnalyzer.class, | |||
NewCoverageAggregator.class); | |||
} | |||
} |
@@ -19,10 +19,18 @@ | |||
*/ | |||
package org.sonar.plugins.core.sensors; | |||
import org.sonar.api.batch.*; | |||
import org.sonar.api.batch.Decorator; | |||
import org.sonar.api.batch.DecoratorContext; | |||
import org.sonar.api.batch.DependsUpon; | |||
import org.sonar.api.batch.Event; | |||
import org.sonar.api.batch.TimeMachine; | |||
import org.sonar.api.batch.TimeMachineQuery; | |||
import org.sonar.api.measures.CoreMetrics; | |||
import org.sonar.api.measures.Measure; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.api.measures.Metric.Level; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.api.profiles.RulesProfile; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.resources.Resource; | |||
@@ -34,17 +42,18 @@ public class GenerateAlertEvents implements Decorator { | |||
private final RulesProfile profile; | |||
private final TimeMachine timeMachine; | |||
private NotificationManager notificationManager; | |||
public GenerateAlertEvents(RulesProfile profile, TimeMachine timeMachine) { | |||
public GenerateAlertEvents(RulesProfile profile, TimeMachine timeMachine, NotificationManager notificationManager) { | |||
this.profile = profile; | |||
this.timeMachine = timeMachine; | |||
this.notificationManager = notificationManager; | |||
} | |||
public boolean shouldExecuteOnProject(Project project) { | |||
return profile != null && profile.getAlerts() != null && profile.getAlerts().size() > 0; | |||
} | |||
@DependsUpon | |||
public Metric dependsUponAlertStatus() { | |||
return CoreMetrics.ALERT_STATUS; | |||
@@ -63,15 +72,41 @@ public class GenerateAlertEvents implements Decorator { | |||
List<Measure> measures = timeMachine.getMeasures(query); | |||
Measure pastStatus = (measures != null && measures.size() == 1 ? measures.get(0) : null); | |||
if (pastStatus != null && pastStatus.getDataAsLevel() != currentStatus.getDataAsLevel()) { | |||
createEvent(context, getName(pastStatus, currentStatus), currentStatus.getAlertText()); | |||
} else if (pastStatus == null && currentStatus.getDataAsLevel() != Metric.Level.OK) { | |||
createEvent(context, getName(currentStatus), currentStatus.getAlertText()); | |||
String alertText = currentStatus.getAlertText(); | |||
Level alertLevel = currentStatus.getDataAsLevel(); | |||
String alertName = null; | |||
boolean isNewAlert = true; | |||
if (pastStatus != null && pastStatus.getDataAsLevel() != alertLevel) { | |||
// The alert status has changed | |||
alertName = getName(pastStatus, currentStatus); | |||
if (pastStatus.getDataAsLevel() != Metric.Level.OK) { | |||
// There was already a Orange/Red alert, so this is no new alert: it has just changed | |||
isNewAlert = false; | |||
} | |||
createEvent(context, alertName, alertText); | |||
notifyUsers(resource, alertName, alertText, alertLevel, isNewAlert); | |||
} else if (pastStatus == null && alertLevel != Metric.Level.OK) { | |||
// There were no defined alerts before, so this one is a new one | |||
alertName = getName(currentStatus); | |||
createEvent(context, alertName, alertText); | |||
notifyUsers(resource, alertName, alertText, alertLevel, isNewAlert); | |||
} | |||
} | |||
protected void notifyUsers(Resource<?> resource, String alertName, String alertText, Level alertLevel, boolean isNewAlert) { | |||
Notification notification = new Notification("alerts") | |||
.setFieldValue("projectName", resource.getLongName()) | |||
.setFieldValue("projectKey", resource.getKey()) | |||
.setFieldValue("projectId", String.valueOf(resource.getId())) | |||
.setFieldValue("alertName", alertName) | |||
.setFieldValue("alertText", alertText) | |||
.setFieldValue("alertLevel", alertLevel.toString()) | |||
.setFieldValue("isNewAlert", Boolean.toString(isNewAlert)); | |||
notificationManager.scheduleForSending(notification); | |||
} | |||
private boolean shouldDecorateResource(Resource resource) { | |||
return ResourceUtils.isRootProject(resource); | |||
} |
@@ -26,13 +26,12 @@ import org.sonar.api.web.WidgetPropertyType; | |||
@WidgetCategory("Hotspots") | |||
@WidgetProperties( | |||
{ | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5"), | |||
@WidgetProperty(key = "defaultSeverity", type = WidgetPropertyType.STRING, description = "Values: BLOCKER, CRITICAL, MAJOR, MINOR, INFO") | |||
} | |||
) | |||
{ | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5"), | |||
@WidgetProperty(key = "defaultSeverity", type = WidgetPropertyType.STRING) | |||
}) | |||
public class HotspotMostViolatedRulesWidget extends CoreWidget { | |||
public HotspotMostViolatedRulesWidget() { | |||
super("hotspot_most_violated_rules", "Most violated rules", "/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_rules.html.erb"); | |||
} | |||
} | |||
} |
@@ -26,12 +26,12 @@ import org.sonar.api.web.WidgetPropertyType; | |||
@WidgetCategory("History") | |||
@WidgetProperties({ | |||
@WidgetProperty(key = "chartTitle", type = WidgetPropertyType.STRING), | |||
@WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC, defaultValue = "ncloc", options = {WidgetConstants.FILTER_OUT_NEW_METRICS}), | |||
@WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC, options = {WidgetConstants.FILTER_OUT_NEW_METRICS}), | |||
@WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC, options = {WidgetConstants.FILTER_OUT_NEW_METRICS}), | |||
@WidgetProperty(key = "hideEvents", type = WidgetPropertyType.BOOLEAN), | |||
@WidgetProperty(key = "chartHeight", type = WidgetPropertyType.INTEGER, defaultValue = "80") | |||
@WidgetProperty(key = "chartTitle", type = WidgetPropertyType.STRING), | |||
@WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC, defaultValue = "ncloc", options = {WidgetConstants.FILTER_OUT_NEW_METRICS}), | |||
@WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC, options = {WidgetConstants.FILTER_OUT_NEW_METRICS}), | |||
@WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC, options = {WidgetConstants.FILTER_OUT_NEW_METRICS}), | |||
@WidgetProperty(key = "hideEvents", type = WidgetPropertyType.BOOLEAN), | |||
@WidgetProperty(key = "chartHeight", type = WidgetPropertyType.INTEGER, defaultValue = "80") | |||
}) | |||
public class TimelineWidget extends CoreWidget { | |||
public TimelineWidget() { |
@@ -25,13 +25,13 @@ import org.sonar.api.web.WidgetProperty; | |||
import org.sonar.api.web.WidgetPropertyType; | |||
@WidgetProperties({ | |||
@WidgetProperty(key = "sizeMetric", type = WidgetPropertyType.METRIC, defaultValue = CoreMetrics.NCLOC_KEY, description = "Metric used for square size"), | |||
@WidgetProperty(key = "colorMetric", type = WidgetPropertyType.METRIC, defaultValue = CoreMetrics.VIOLATIONS_DENSITY_KEY, description = "Metric used for square color"), | |||
@WidgetProperty(key = "heightInPercents", type = WidgetPropertyType.INTEGER, optional = true, defaultValue = "55", description = "Height in percents of width") | |||
@WidgetProperty(key = "sizeMetric", type = WidgetPropertyType.METRIC, defaultValue = CoreMetrics.NCLOC_KEY), | |||
@WidgetProperty(key = "colorMetric", type = WidgetPropertyType.METRIC, defaultValue = CoreMetrics.VIOLATIONS_DENSITY_KEY), | |||
@WidgetProperty(key = "heightInPercents", type = WidgetPropertyType.INTEGER, optional = true, defaultValue = "55") | |||
}) | |||
public class TreemapWidget extends CoreWidget { | |||
public TreemapWidget() { | |||
// do not use the id "treemap" to avoid conflict with the same CSS class | |||
super("treemap-widget", "Treemap of Components", "/org/sonar/plugins/core/widgets/treemap.html.erb"); | |||
} | |||
} | |||
} |
@@ -27,11 +27,10 @@ import org.sonar.plugins.core.widgets.CoreWidget; | |||
@WidgetCategory({"Reviews"}) | |||
@WidgetProperties({ | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", | |||
description = "Maximum number of reviews displayed at the same time.") | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5") | |||
}) | |||
public class FalsePositiveReviewsWidget extends CoreWidget { | |||
public FalsePositiveReviewsWidget() { | |||
super("false_positive_reviews", "False positive open reviews", "/org/sonar/plugins/core/widgets/reviews/false_positive_reviews.html.erb"); | |||
} | |||
} | |||
} |
@@ -27,11 +27,10 @@ import org.sonar.plugins.core.widgets.CoreWidget; | |||
@WidgetCategory({"Reviews"}) | |||
@WidgetProperties({ | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", | |||
description = "Maximum number of reviews displayed at the same time.") | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5") | |||
}) | |||
public class MyReviewsWidget extends CoreWidget { | |||
public MyReviewsWidget() { | |||
super("my_reviews", "My active reviews", "/org/sonar/plugins/core/widgets/reviews/my_reviews.html.erb"); | |||
} | |||
} | |||
} |
@@ -27,11 +27,10 @@ import org.sonar.plugins.core.widgets.CoreWidget; | |||
@WidgetCategory({"Action plans", "Reviews"}) | |||
@WidgetProperties({ | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", | |||
description = "Maximum number of reviews displayed at the same time.") | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5") | |||
}) | |||
public class PlannedReviewsWidget extends CoreWidget { | |||
public PlannedReviewsWidget() { | |||
super("planned_reviews", "Planned reviews", "/org/sonar/plugins/core/widgets/reviews/planned_reviews.html.erb"); | |||
} | |||
} | |||
} |
@@ -27,11 +27,10 @@ import org.sonar.plugins.core.widgets.CoreWidget; | |||
@WidgetCategory({"Reviews"}) | |||
@WidgetProperties({ | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", | |||
description = "Maximum number of reviews displayed at the same time.") | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5") | |||
}) | |||
public class ProjectReviewsWidget extends CoreWidget { | |||
public ProjectReviewsWidget() { | |||
super("project_reviews", "Project active reviews", "/org/sonar/plugins/core/widgets/reviews/project_reviews.html.erb"); | |||
} | |||
} | |||
} |
@@ -27,11 +27,10 @@ import org.sonar.plugins.core.widgets.CoreWidget; | |||
@WidgetCategory({"Action plans", "Reviews"}) | |||
@WidgetProperties({ | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", | |||
description = "Maximum number of reviews displayed at the same time.") | |||
@WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5") | |||
}) | |||
public class UnplannedReviewsWidget extends CoreWidget { | |||
public UnplannedReviewsWidget() { | |||
super("unplanned_reviews", "Unplanned reviews", "/org/sonar/plugins/core/widgets/reviews/unplanned_reviews.html.erb"); | |||
} | |||
} | |||
} |
@@ -835,6 +835,16 @@ widget.complexity.total=Total | |||
widget.custom_measures.name=Custom Measures | |||
widget.custom_measures.description=Display a list of selected measures. | |||
widget.custom_measures.property.metric1.name=Metric 1 | |||
widget.custom_measures.property.metric2.name=Metric 2 | |||
widget.custom_measures.property.metric3.name=Metric 3 | |||
widget.custom_measures.property.metric4.name=Metric 4 | |||
widget.custom_measures.property.metric5.name=Metric 5 | |||
widget.custom_measures.property.metric6.name=Metric 6 | |||
widget.custom_measures.property.metric7.name=Metric 7 | |||
widget.custom_measures.property.metric8.name=Metric 8 | |||
widget.custom_measures.property.metric9.name=Metric 9 | |||
widget.custom_measures.property.metric10.name=Metric 10 | |||
widget.description.name=Description | |||
widget.description.description=Displays general project information | |||
@@ -880,9 +890,28 @@ widget.size.projects.suffix=\ projects | |||
widget.timeline.name=Timeline | |||
widget.timeline.description=Displays up to 3 metrics on a history chart. | |||
widget.timeline.timeline_not_displayed=No history | |||
widget.timeline.property.chartTitle.name=Chart title | |||
widget.timeline.property.metric1.name=Metric 1 | |||
widget.timeline.property.metric2.name=Metric 2 | |||
widget.timeline.property.metric3.name=Metric 3 | |||
widget.timeline.property.hideEvents.name=Hide events | |||
widget.timeline.property.chartHeight.name=Chart height | |||
widget.time_machine.name=History Table | |||
widget.time_machine.description=Displays up to 10 metrics in a table, showing their value for a specified number of past snapshots. | |||
widget.time_machine.property.title.name=Title | |||
widget.time_machine.property.numberOfColumns.name=Number of columns | |||
widget.time_machine.property.displaySparkLine.name=Display spark line | |||
widget.time_machine.property.metric1.name=Metric 1 | |||
widget.time_machine.property.metric2.name=Metric 2 | |||
widget.time_machine.property.metric3.name=Metric 3 | |||
widget.time_machine.property.metric4.name=Metric 4 | |||
widget.time_machine.property.metric5.name=Metric 5 | |||
widget.time_machine.property.metric6.name=Metric 6 | |||
widget.time_machine.property.metric7.name=Metric 7 | |||
widget.time_machine.property.metric8.name=Metric 8 | |||
widget.time_machine.property.metric9.name=Metric 9 | |||
widget.time_machine.property.metric10.name=Metric 10 | |||
widget.file_design.name=File design | |||
widget.file_design.description=Reports on files dependency cycles and tangle index. | |||
@@ -902,26 +931,39 @@ widget.hotspot_metric.name=Metric hotspot | |||
widget.hotspot_metric.description=Shows the files that have the worst result for a specific metric. | |||
widget.hotspot_metric.more=More | |||
widget.hotspot_metric.hotspots_by_x=Hotspots by {0} | |||
widget.hotspot_metric.property.title.name=Title | |||
widget.hotspot_metric.property.metric.name=Metric | |||
widget.hotspot_metric.property.numberOfLines.name=Number of lines | |||
widget.hotspot_most_violated_rules.name=Most violated rules | |||
widget.hotspot_most_violated_rules.name_when_period=Most new violated rules | |||
widget.hotspot_most_violated_rules.description=Shows the rules that are the most violated. | |||
widget.hotspot_most_violated_rules.no_violation_for_severity=No result | |||
widget.hotspot_most_violated_rules.any_severity=Any severity | |||
widget.hotspot_most_violated_rules.property.numberOfLines.name=Number of lines | |||
widget.hotspot_most_violated_rules.property.defaultSeverity.name=Default severity | |||
widget.hotspot_most_violated_rules.property.defaultSeverity.desc=Values: BLOCKER, CRITICAL, MAJOR, MINOR, INFO | |||
widget.hotspot_most_violated_resources.name=Most violated resources | |||
widget.hotspot_most_violated_resources.description=Shows the resources that have the most violations. | |||
widget.hotspot_most_violated_resources.no_resource=No result | |||
widget.hotspot_most_violated_resources.property.numberOfLines.name=Number of lines | |||
widget.my_reviews.name=My active reviews | |||
widget.my_reviews.description=Shows open/reopened reviews assigned to the current user. | |||
widget.my_reviews.no_review=No review. | |||
widget.my_reviews.property.numberOfLines.name=Number of lines | |||
widget.my_reviews.property.numberOfLines.desc=Maximum number of reviews displayed at the same time. | |||
widget.project_reviews.name=Active reviews | |||
widget.project_reviews.description=Shows all the open/reopened reviews. | |||
widget.project_reviews.property.numberOfLines.name=Number of lines | |||
widget.project_reviews.property.numberOfLines.desc=Maximum number of reviews displayed at the same time. | |||
widget.false_positive_reviews.name=False positives | |||
widget.false_positive_reviews.description=Shows all the false positives found on the project. | |||
widget.false_positive_reviews.property.numberOfLines.name=Number of lines | |||
widget.false_positive_reviews.property.numberOfLines.desc=Maximum number of reviews displayed at the same time. | |||
widget.reviews_per_developer.name=Active reviews per developer | |||
widget.reviews_per_developer.description=Shows the number of open/reopened reviews per developer. | |||
@@ -929,6 +971,7 @@ widget.reviews_per_developer.not_assigned=Not assigned | |||
widget.action_plans.name=Action plans | |||
widget.action_plans.description=Shows all the open action plans of the project | |||
widget.action_plans.property.showClosedReviews.name=Show Closed Reviews | |||
widget.action_plans.title=Open action plans | |||
widget.action_plans.no_action_plan=No action plan | |||
widget.action_plans.x_open_reviews={0} open reviews | |||
@@ -936,9 +979,13 @@ widget.action_plans.x_open_reviews={0} open reviews | |||
widget.planned_reviews.name=Planned reviews | |||
widget.planned_reviews.description=Shows all the planned reviews of the project, gathered by action plan | |||
widget.planned_reviews.no_action_plan=No action plan | |||
widget.planned_reviews.property.numberOfLines.name=Number of lines | |||
widget.planned_reviews.property.numberOfLines.desc=Maximum number of reviews displayed at the same time. | |||
widget.unplanned_reviews.name=Unplanned reviews | |||
widget.unplanned_reviews.description=Shows all the reviews of the project that are not planned yet in an action plan | |||
widget.unplanned_reviews.property.numberOfLines.name=Number of lines | |||
widget.unplanned_reviews.property.numberOfLines.desc=Maximum number of reviews displayed at the same time. | |||
widget.reviews_metrics.name=Review Activity | |||
widget.reviews_metrics.description=Reports metrics about reviews | |||
@@ -952,13 +999,12 @@ widget.reviews_metrics.added_unreviewed_violations=Added: | |||
widget.treemap-widget.name=Treemap of components | |||
widget.treemap-widget.description=Displays a treemap of all direct components of the selected resource | |||
widget.image.name=Image | |||
widget.image.description=Shows an image with a link | |||
widget.filter.name=Filter | |||
widget.filter.description=Shows a pre-configured filter | |||
widget.filter.edit=Edit my filters | |||
widget.treemap-widget.property.sizeMetric.name=Size metric | |||
widget.treemap-widget.property.sizeMetric.description=Metric used for square size | |||
widget.treemap-widget.property.colorMetric.name=Color metric | |||
widget.treemap-widget.property.colorMetric.description=Metric used for square color | |||
widget.treemap-widget.property.heightInPercents.name=Height | |||
widget.treemap-widget.property.heightInPercents.description=Height in percents of width | |||
widget.lcom4.name=LCOM4 | |||
widget.lcom4.description=Reports on LCOM4 average and distribution. | |||
@@ -971,8 +1017,6 @@ widget.rfc.description=Reports on RFC average and distribution. | |||
widget.rfc.title=Response for Class | |||
widget.rfc.per_class.suffix=\ /class | |||
widget.resource_id=project | |||
widget.welcome.name=Welcome | |||
widget.welcome.description=Welcome message used to provide links to the most valuable resources like documentation and support | |||
widget.welcome.html=<h3 class="marginbottom5">Welcome to Sonar Dashboard</h3>\ | |||
@@ -987,9 +1031,26 @@ widget.welcome.html=<h3 class="marginbottom5">Welcome to Sonar Dashboard</h3>\ | |||
widget.measure_filter_list.name=Measure Filter as List | |||
widget.measure_filter_list.description=Displays the result of a pre-configured measure filter as a list | |||
widget.measure_filter_list.property.filter.name=Filter | |||
widget.measure_filter_list.property.pageSize.name=Page size | |||
widget.measure_filter_list.property.displayFilterDescription.name=Display filter description | |||
widget.measure_filter_treemap.name=Measure Filter as Treemap | |||
widget.measure_filter_treemap.description=Displays the result of pre-configured measure filter as a Treemap | |||
widget.measure_filter_treemap.property.filter.name=Filter | |||
widget.measure_filter_treemap.property.sizeMetric.name=Size metric | |||
widget.measure_filter_treemap.property.colorMetric.name=Color metric | |||
widget.measure_filter_treemap.property.heightInPercents.name=Height | |||
widget.measure_filter_treemap.property.heightInPercents.description=Height in percents of width | |||
widget.measure_filter_treemap.property.displayFilterDescription.name=Display filter description | |||
# Below are labels used in widget edition pages | |||
widget.image.name=Image | |||
widget.image.description=Shows an image with a link | |||
widget.filter.name=Filter | |||
widget.filter.description=Shows a pre-configured filter | |||
widget.filter.edit=Edit my filters | |||
widget.resource_id=Project | |||
#------------------------------------------------------------------------------ | |||
@@ -1434,6 +1495,7 @@ server_id_configuration.generation_error=Organisation and/or IP address are not | |||
notification.channel.EmailNotificationChannel=Email | |||
notification.dispatcher.ChangesInReviewAssignedToMeOrCreatedByMe=Changes in review assigned to me or created by me | |||
notification.dispatcher.NewViolationsOnMyFavouriteProject=New violations on my favourite projects introduced during the first differential view period | |||
notification.dispatcher.AlertsOnMyFavouriteProject=Alerts on my favourite projects | |||
#------------------------------------------------------------------------------ | |||
@@ -1519,6 +1581,11 @@ bulk_deletion.deletion_manager.currently_deleting_x_out_of_x=Currently deleting | |||
bulk_deletion.deletion_manager.deletion_completed=Resource deletion completed. | |||
bulk_deletion.deletion_manager.however_failures_occurred=However, some failures occurred. | |||
bulk_deletion.started_since_x=Started {0} ago | |||
bulk_deletion.ghosts=Ghosts | |||
bulk_deletion.ghosts.description=A ghost is the result of constantly failed attempts to analyse a project. In such a case, the project is not linked to any successful analysis, and therefore cannot be displayed in Sonar.<br/>When the user authentication is forced, leaving a ghost can even prevent further analyses of the corresponding project. | |||
bulk_deletion.no_ghosts=There is currently no ghost. | |||
bulk_deletion.following_ghosts_can_be_deleted=The following ghosts can be safely deleted: | |||
bulk_deletion.delete_all_ghosts=Delete all ghosts | |||
#------------------------------------------------------------------------------ |
@@ -36,7 +36,7 @@ | |||
end | |||
%> | |||
<table class="width100 actionPlans"> | |||
<table class="width100 <%= show_closed_reviews ? 'actionPlans' : 'data' -%>"> | |||
<thead> | |||
<tr> | |||
<th colspan="<%= show_closed_reviews ? '3' : '4' %>"></th> | |||
@@ -46,7 +46,7 @@ | |||
<% | |||
open_action_plans.each do |plan| | |||
%> | |||
<tr> | |||
<tr class="<%= show_closed_reviews ? '' : cycle("even", "odd", :name => "action_plan_" + widget.id.to_s) -%>"> | |||
<td class="nowrap <%= line_class -%>"><%= h(plan.name) -%></td> | |||
<td class="nowrap small <%= line_class -%> <%= 'over-due' if plan.over_due? -%>" style="text-align: right; padding-left:10px"><%= plan.deadline ? plan.deadline.strftime("%d %b %Y") : ' ' -%></td> | |||
@@ -62,7 +62,7 @@ | |||
else | |||
open_reviews_size = plan.open_reviews.size | |||
%> | |||
<td class="right note <%= line_class -%>" style="padding-left:20px"> | |||
<td class="right <%= line_class -%>" style="padding-left:20px"> | |||
<% if open_reviews_size == 0 %> | |||
<%= open_reviews_size -%> | |||
<% else %> |
@@ -28,21 +28,27 @@ import org.sonar.api.batch.TimeMachineQuery; | |||
import org.sonar.api.measures.CoreMetrics; | |||
import org.sonar.api.measures.Measure; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.api.profiles.Alert; | |||
import org.sonar.api.profiles.RulesProfile; | |||
import org.sonar.api.resources.File; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.test.ProjectTestBuilder; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.hamcrest.CoreMatchers.is; | |||
import static org.junit.Assert.assertThat; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Matchers.anyString; | |||
import static org.mockito.Matchers.eq; | |||
import static org.mockito.Matchers.isNull; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.never; | |||
import static org.mockito.Mockito.times; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.when; | |||
@@ -51,6 +57,7 @@ public class GenerateAlertEventsTest { | |||
private DecoratorContext context; | |||
private RulesProfile profile; | |||
private TimeMachine timeMachine; | |||
private NotificationManager notificationManager; | |||
private Project project; | |||
@Before | |||
@@ -58,12 +65,18 @@ public class GenerateAlertEventsTest { | |||
context = mock(DecoratorContext.class); | |||
timeMachine = mock(TimeMachine.class); | |||
profile = mock(RulesProfile.class); | |||
decorator = new GenerateAlertEvents(profile, timeMachine); | |||
notificationManager = mock(NotificationManager.class); | |||
decorator = new GenerateAlertEvents(profile, timeMachine, notificationManager); | |||
project = new ProjectTestBuilder().build(); | |||
} | |||
@Test | |||
public void doNotDecorateIfNoThresholds() { | |||
public void shouldDependUponAlertStatus() { | |||
assertThat(decorator.dependsUponAlertStatus()).isEqualTo(CoreMetrics.ALERT_STATUS); | |||
} | |||
@Test | |||
public void shouldNotDecorateIfNoThresholds() { | |||
assertThat(decorator.shouldExecuteOnProject(project), is(false)); | |||
} | |||
@@ -73,18 +86,30 @@ public class GenerateAlertEventsTest { | |||
assertThat(decorator.shouldExecuteOnProject(project), is(true)); | |||
} | |||
@Test | |||
public void shouldNotDecorateIfNotRootProject() { | |||
decorator.decorate(new File("Foo"), context); | |||
verify(context, never()).createEvent(anyString(), anyString(), anyString(), (Date) isNull()); | |||
} | |||
@Test | |||
public void shouldCreateEventWhenNewErrorAlert() { | |||
when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.ERROR, "desc")); | |||
decorator.decorate(project, context); | |||
verify(context).createEvent(Metric.Level.ERROR.getColorName(), "desc", Event.CATEGORY_ALERT, null); | |||
verifyNotificationSent("Red", "desc", "ERROR", "true"); | |||
} | |||
@Test | |||
public void shouldCreateEventWhenNewWarnAlert() { | |||
when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.WARN, "desc")); | |||
decorator.decorate(project, context); | |||
verify(context).createEvent(Metric.Level.WARN.getColorName(), "desc", Event.CATEGORY_ALERT, null); | |||
verifyNotificationSent("Orange", "desc", "WARN", "true"); | |||
} | |||
@Test | |||
@@ -95,6 +120,7 @@ public class GenerateAlertEventsTest { | |||
decorator.decorate(project, context); | |||
verify(context).createEvent("Red (was Orange)", "desc", Event.CATEGORY_ALERT, null); | |||
verifyNotificationSent("Red (was Orange)", "desc", "ERROR", "false"); | |||
} | |||
@Test | |||
@@ -105,6 +131,18 @@ public class GenerateAlertEventsTest { | |||
decorator.decorate(project, context); | |||
verify(context).createEvent("Green (was Red)", null, Event.CATEGORY_ALERT, null); | |||
verifyNotificationSent("Green (was Red)", null, "OK", "false"); | |||
} | |||
@Test | |||
public void shouldCreateEventWhenOkToError() { | |||
when(timeMachine.getMeasures(any(TimeMachineQuery.class))).thenReturn(Arrays.asList(newAlertStatus(Metric.Level.OK, null))); | |||
when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.ERROR, "desc")); | |||
decorator.decorate(project, context); | |||
verify(context).createEvent("Red (was Green)", "desc", Event.CATEGORY_ALERT, null); | |||
verifyNotificationSent("Red (was Green)", "desc", "ERROR", "true"); | |||
} | |||
@Test | |||
@@ -115,6 +153,7 @@ public class GenerateAlertEventsTest { | |||
decorator.decorate(project, context); | |||
verify(context).createEvent("Orange (was Red)", "desc", Event.CATEGORY_ALERT, null); | |||
verifyNotificationSent("Orange (was Red)", "desc", "WARN", "false"); | |||
} | |||
@Test | |||
@@ -122,6 +161,7 @@ public class GenerateAlertEventsTest { | |||
decorator.decorate(project, context); | |||
verify(context, never()).createEvent(anyString(), anyString(), anyString(), (Date) isNull()); | |||
verify(notificationManager, never()).scheduleForSending(any(Notification.class)); | |||
} | |||
@Test | |||
@@ -132,6 +172,7 @@ public class GenerateAlertEventsTest { | |||
decorator.decorate(project, context); | |||
verify(context, never()).createEvent(anyString(), anyString(), anyString(), (Date) isNull()); | |||
verify(notificationManager, never()).scheduleForSending(any(Notification.class)); | |||
} | |||
@Test | |||
@@ -142,6 +183,7 @@ public class GenerateAlertEventsTest { | |||
decorator.decorate(project, context); | |||
verify(context, never()).createEvent(anyString(), anyString(), anyString(), (Date) isNull()); | |||
verify(notificationManager, never()).scheduleForSending(any(Notification.class)); | |||
} | |||
private Measure newAlertStatus(Metric.Level level, String label) { | |||
@@ -150,4 +192,16 @@ public class GenerateAlertEventsTest { | |||
measure.setAlertText(label); | |||
return measure; | |||
} | |||
private void verifyNotificationSent(String alertName, String alertText, String alertLevel, String isNewAlert) { | |||
Notification notification = new Notification("alerts") | |||
.setFieldValue("projectName", project.getLongName()) | |||
.setFieldValue("projectKey", project.getKey()) | |||
.setFieldValue("projectId", String.valueOf(project.getId())) | |||
.setFieldValue("alertName", alertName) | |||
.setFieldValue("alertText", alertText) | |||
.setFieldValue("alertLevel", alertLevel) | |||
.setFieldValue("isNewAlert", isNewAlert); | |||
verify(notificationManager, times(1)).scheduleForSending(eq(notification)); | |||
} | |||
} |
@@ -22,6 +22,8 @@ package org.sonar.plugins.emailnotifications; | |||
import com.google.common.collect.ImmutableList; | |||
import org.sonar.api.ServerExtension; | |||
import org.sonar.api.SonarPlugin; | |||
import org.sonar.plugins.emailnotifications.alerts.AlertsEmailTemplate; | |||
import org.sonar.plugins.emailnotifications.alerts.AlertsOnMyFavouriteProject; | |||
import org.sonar.plugins.emailnotifications.newviolations.NewViolationsEmailTemplate; | |||
import org.sonar.plugins.emailnotifications.newviolations.NewViolationsOnMyFavouriteProject; | |||
import org.sonar.plugins.emailnotifications.reviews.ChangesInReviewAssignedToMeOrCreatedByMe; | |||
@@ -32,10 +34,16 @@ import java.util.List; | |||
public class EmailNotificationsPlugin extends SonarPlugin { | |||
public List<Class<? extends ServerExtension>> getExtensions() { | |||
return ImmutableList.of( | |||
ChangesInReviewAssignedToMeOrCreatedByMe.class, | |||
EmailNotificationChannel.class, | |||
NewViolationsEmailTemplate.class, | |||
// Notify incoming violations on my favourite projects | |||
NewViolationsOnMyFavouriteProject.class, | |||
ReviewEmailTemplate.class); | |||
NewViolationsEmailTemplate.class, | |||
// Notify reviews changes | |||
ChangesInReviewAssignedToMeOrCreatedByMe.class, | |||
ReviewEmailTemplate.class, | |||
// Notify alerts on my favourite projects | |||
AlertsOnMyFavouriteProject.class, | |||
AlertsEmailTemplate.class | |||
); | |||
} | |||
} |
@@ -0,0 +1,107 @@ | |||
/* | |||
* Sonar, open source software quality management 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 | |||
*/ | |||
package org.sonar.plugins.emailnotifications.alerts; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.plugins.emailnotifications.api.EmailMessage; | |||
import org.sonar.plugins.emailnotifications.api.EmailTemplate; | |||
/** | |||
* Creates email message for notification "alerts". | |||
* | |||
* @since 3.5 | |||
*/ | |||
public class AlertsEmailTemplate extends EmailTemplate { | |||
private EmailSettings configuration; | |||
public AlertsEmailTemplate(EmailSettings configuration) { | |||
this.configuration = configuration; | |||
} | |||
@Override | |||
public EmailMessage format(Notification notification) { | |||
if (!"alerts".equals(notification.getType())) { | |||
return null; | |||
} | |||
// Retrieve useful values | |||
String projectId = notification.getFieldValue("projectId"); | |||
String projectKey = notification.getFieldValue("projectKey"); | |||
String projectName = notification.getFieldValue("projectName"); | |||
String alertName = notification.getFieldValue("alertName"); | |||
String alertText = notification.getFieldValue("alertText"); | |||
String alertLevel = notification.getFieldValue("alertLevel"); | |||
boolean isNewAlert = Boolean.valueOf(notification.getFieldValue("isNewAlert")); | |||
// Generate text | |||
String subject = generateSubject(projectName, alertLevel, isNewAlert); | |||
String messageBody = generateMessageBody(projectName, projectKey, alertName, alertText, isNewAlert); | |||
// And finally return the email that will be sent | |||
return new EmailMessage() | |||
.setMessageId("alerts/" + projectId) | |||
.setSubject(subject) | |||
.setMessage(messageBody); | |||
} | |||
private String generateSubject(String projectName, String alertLevel, boolean isNewAlert) { | |||
StringBuilder subjectBuilder = new StringBuilder(); | |||
if (Metric.Level.OK.toString().equals(alertLevel)) { | |||
subjectBuilder.append("\"").append(projectName).append("\" is back to green"); | |||
} else if (isNewAlert) { | |||
subjectBuilder.append("New alert on \"").append(projectName).append("\""); | |||
} else { | |||
subjectBuilder.append("Alert level changed on \"").append(projectName).append("\""); | |||
} | |||
return subjectBuilder.toString(); | |||
} | |||
private String generateMessageBody(String projectName, String projectKey, String alertName, String alertText, boolean isNewAlert) { | |||
StringBuilder messageBody = new StringBuilder(); | |||
messageBody.append("Project: ").append(projectName).append("\n"); | |||
messageBody.append("Alert level: ").append(alertName).append("\n\n"); | |||
String[] alerts = StringUtils.split(alertText, ","); | |||
if (alerts.length > 0) { | |||
if (isNewAlert) { | |||
messageBody.append("New alert"); | |||
} else { | |||
messageBody.append("Alert"); | |||
} | |||
if (alerts.length == 1) { | |||
messageBody.append(": ").append(alerts[0].trim()).append("\n"); | |||
} else { | |||
messageBody.append("s:\n"); | |||
for (String alert : alerts) { | |||
messageBody.append(" - ").append(alert.trim()).append("\n"); | |||
} | |||
} | |||
} | |||
messageBody.append("\n").append("See it in Sonar: ").append(configuration.getServerBaseURL()).append("/dashboard/index/").append(projectKey); | |||
return messageBody.toString(); | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* Sonar, open source software quality management 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 | |||
*/ | |||
package org.sonar.plugins.emailnotifications.alerts; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.core.properties.PropertiesDao; | |||
import java.util.List; | |||
/** | |||
* This dispatcher means: "notify me when alerts are raised on projects that I flagged as favourite". | |||
* | |||
* @since 3.5 | |||
*/ | |||
public class AlertsOnMyFavouriteProject extends NotificationDispatcher { | |||
private PropertiesDao propertiesDao; | |||
public AlertsOnMyFavouriteProject(PropertiesDao propertiesDao) { | |||
this.propertiesDao = propertiesDao; | |||
} | |||
@Override | |||
public void dispatch(Notification notification, Context context) { | |||
if (StringUtils.equals(notification.getType(), "alerts")) { | |||
Long projectId = Long.parseLong(notification.getFieldValue("projectId")); | |||
List<String> userLogins = propertiesDao.findUserIdsForFavouriteResource(projectId); | |||
for (String userLogin : userLogins) { | |||
context.addUser(userLogin); | |||
} | |||
} | |||
} | |||
} |
@@ -26,6 +26,6 @@ import static org.fest.assertions.Assertions.assertThat; | |||
public class EmailNotificationsPluginTest { | |||
@Test | |||
public void should_get_extensions() { | |||
assertThat(new EmailNotificationsPlugin().getExtensions()).hasSize(5); | |||
assertThat(new EmailNotificationsPlugin().getExtensions()).hasSize(7); | |||
} | |||
} |
@@ -0,0 +1,131 @@ | |||
/* | |||
* Sonar, open source software quality management 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 | |||
*/ | |||
package org.sonar.plugins.emailnotifications.alerts; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.plugins.emailnotifications.api.EmailMessage; | |||
import static org.hamcrest.Matchers.is; | |||
import static org.hamcrest.Matchers.nullValue; | |||
import static org.junit.Assert.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class AlertsEmailTemplateTest { | |||
private AlertsEmailTemplate template; | |||
@Before | |||
public void setUp() { | |||
EmailSettings configuration = mock(EmailSettings.class); | |||
when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org"); | |||
template = new AlertsEmailTemplate(configuration); | |||
} | |||
@Test | |||
public void shouldNotFormatIfNotCorrectNotification() { | |||
Notification notification = new Notification("other-notif"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message, nullValue()); | |||
} | |||
@Test | |||
public void shouldFormatAlertWithSeveralMessages() { | |||
Notification notification = createNotification("Orange (was Red)", "violations > 4, coverage < 75%", "WARN", "false"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId(), is("alerts/45")); | |||
assertThat(message.getSubject(), is("Alert level changed on \"Foo\"")); | |||
assertThat(message.getMessage(), is("" + | |||
"Project: Foo\n" + | |||
"Alert level: Orange (was Red)\n" + | |||
"\n" + | |||
"Alerts:\n" + | |||
" - violations > 4\n" + | |||
" - coverage < 75%\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo")); | |||
} | |||
@Test | |||
public void shouldFormatNewAlertWithSeveralMessages() { | |||
Notification notification = createNotification("Orange (was Red)", "violations > 4, coverage < 75%", "WARN", "true"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId(), is("alerts/45")); | |||
assertThat(message.getSubject(), is("New alert on \"Foo\"")); | |||
assertThat(message.getMessage(), is("" + | |||
"Project: Foo\n" + | |||
"Alert level: Orange (was Red)\n" + | |||
"\n" + | |||
"New alerts:\n" + | |||
" - violations > 4\n" + | |||
" - coverage < 75%\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo")); | |||
} | |||
@Test | |||
public void shouldFormatNewAlertWithOneMessage() { | |||
Notification notification = createNotification("Orange (was Red)", "violations > 4", "WARN", "true"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId(), is("alerts/45")); | |||
assertThat(message.getSubject(), is("New alert on \"Foo\"")); | |||
assertThat(message.getMessage(), is("" + | |||
"Project: Foo\n" + | |||
"Alert level: Orange (was Red)\n" + | |||
"\n" + | |||
"New alert: violations > 4\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo")); | |||
} | |||
@Test | |||
public void shouldFormatBackToGreenMessage() { | |||
Notification notification = createNotification("Green (was Red)", "", "OK", "false"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId(), is("alerts/45")); | |||
assertThat(message.getSubject(), is("\"Foo\" is back to green")); | |||
assertThat(message.getMessage(), is("" + | |||
"Project: Foo\n" + | |||
"Alert level: Green (was Red)\n" + | |||
"\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo")); | |||
} | |||
private Notification createNotification(String alertName, String alertText, String alertLevel, String isNewAlert) { | |||
Notification notification = new Notification("alerts") | |||
.setFieldValue("projectName", "Foo") | |||
.setFieldValue("projectKey", "org.sonar.foo:foo") | |||
.setFieldValue("projectId", "45") | |||
.setFieldValue("alertName", alertName) | |||
.setFieldValue("alertText", alertText) | |||
.setFieldValue("alertLevel", alertLevel) | |||
.setFieldValue("isNewAlert", isNewAlert); | |||
return notification; | |||
} | |||
} |
@@ -0,0 +1,62 @@ | |||
/* | |||
* Sonar, open source software quality management 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 | |||
*/ | |||
package org.sonar.plugins.emailnotifications.alerts; | |||
import com.google.common.collect.Lists; | |||
import org.junit.Test; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.core.properties.PropertiesDao; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.never; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.when; | |||
public class AlertsOnMyFavouriteProjectTest { | |||
@Test | |||
public void shouldNotDispatchIfNotNewViolationsNotification() throws Exception { | |||
NotificationDispatcher.Context context = mock(NotificationDispatcher.Context.class); | |||
AlertsOnMyFavouriteProject dispatcher = new AlertsOnMyFavouriteProject(null); | |||
Notification notification = new Notification("other-notif"); | |||
dispatcher.dispatch(notification, context); | |||
verify(context, never()).addUser(any(String.class)); | |||
} | |||
@Test | |||
public void shouldDispatchToUsersWhoHaveFlaggedProjectAsFavourite() { | |||
NotificationDispatcher.Context context = mock(NotificationDispatcher.Context.class); | |||
PropertiesDao propertiesDao = mock(PropertiesDao.class); | |||
when(propertiesDao.findUserIdsForFavouriteResource(34L)).thenReturn(Lists.newArrayList("user1", "user2")); | |||
AlertsOnMyFavouriteProject dispatcher = new AlertsOnMyFavouriteProject(propertiesDao); | |||
Notification notification = new Notification("alerts").setFieldValue("projectId", "34"); | |||
dispatcher.dispatch(notification, context); | |||
verify(context).addUser("user1"); | |||
verify(context).addUser("user2"); | |||
verifyNoMoreInteractions(context); | |||
} | |||
} |
@@ -69,7 +69,7 @@ | |||
</prerequisites> | |||
<properties> | |||
<sonarJava.version>1.2-SNAPSHOT</sonarJava.version> | |||
<sonarJava.version>1.1</sonarJava.version> | |||
<sonarGwt.version>3.3.1</sonarGwt.version> | |||
<h2.version>1.3.167</h2.version> | |||
<jetty.version>6.1.25</jetty.version> |
@@ -47,7 +47,7 @@ public class BatchSettings extends Settings { | |||
this(bootstrapSettings, propertyDefinitions, null, client, deprecatedConfiguration, globalProperties); | |||
} | |||
public BatchSettings(BootstrapSettings bootstrapSettings, PropertyDefinitions propertyDefinitions, ProjectReactor reactor, | |||
public BatchSettings(BootstrapSettings bootstrapSettings, PropertyDefinitions propertyDefinitions, @Nullable ProjectReactor reactor, | |||
ServerClient client, Configuration deprecatedConfiguration, GlobalBatchProperties globalProperties) { | |||
super(propertyDefinitions); | |||
this.deprecatedConfiguration = deprecatedConfiguration; | |||
@@ -58,7 +58,7 @@ public class BatchSettings extends Settings { | |||
BatchSettings() { | |||
} | |||
private void init(BootstrapSettings bootstrapSettings, ProjectReactor reactor, ServerClient client, | |||
private void init(BootstrapSettings bootstrapSettings, @Nullable ProjectReactor reactor, ServerClient client, | |||
GlobalBatchProperties globalProperties) { | |||
LoggerFactory.getLogger(BatchSettings.class).info("Load batch settings"); | |||
@@ -27,6 +27,8 @@ import org.sonar.batch.FakeMavenPluginExecutor; | |||
import org.sonar.batch.MavenPluginExecutor; | |||
import org.sonar.core.config.Logback; | |||
import javax.annotation.Nullable; | |||
/** | |||
* Level 1 components | |||
*/ | |||
@@ -37,11 +39,15 @@ public class BootstrapModule extends Module { | |||
private GlobalBatchProperties globalProperties; | |||
private String taskCommand; | |||
/** | |||
* @deprecated Use {@link #BootstrapModule(GlobalBatchProperties, String, ProjectReactor, Object...)} | |||
*/ | |||
@Deprecated | |||
public BootstrapModule(ProjectReactor reactor, Object... boostrapperComponents) { | |||
this(new GlobalBatchProperties(), null, reactor, boostrapperComponents); | |||
} | |||
public BootstrapModule(GlobalBatchProperties globalProperties, String taskCommand, ProjectReactor reactor, | |||
public BootstrapModule(GlobalBatchProperties globalProperties, @Nullable String taskCommand, @Nullable ProjectReactor reactor, | |||
Object... boostrapperComponents) { | |||
this.globalProperties = globalProperties; | |||
this.taskCommand = taskCommand; |
@@ -26,6 +26,8 @@ import org.sonar.batch.tasks.InspectionTask; | |||
import org.sonar.batch.tasks.ListTasksTask; | |||
import org.sonar.batch.tasks.Tasks; | |||
import javax.annotation.Nullable; | |||
/** | |||
* Level-2 components. Collect tasks definitions. | |||
*/ | |||
@@ -33,7 +35,7 @@ public class TaskBootstrapModule extends Module { | |||
private String taskCommand; | |||
public TaskBootstrapModule(String taskCommand) { | |||
public TaskBootstrapModule(@Nullable String taskCommand) { | |||
this.taskCommand = taskCommand; | |||
} | |||
@@ -29,6 +29,8 @@ import org.sonar.api.task.TaskComponent; | |||
import org.sonar.api.task.TaskDefinition; | |||
import org.sonar.api.utils.SonarException; | |||
import javax.annotation.Nullable; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.regex.Pattern; | |||
@@ -49,7 +51,7 @@ public class Tasks implements TaskComponent { | |||
this.taskDefinitions = taskDefinitions; | |||
} | |||
public TaskDefinition getTaskDefinition(String command) { | |||
public TaskDefinition getTaskDefinition(@Nullable String command) { | |||
String finalCommand = command; | |||
if (StringUtils.isBlank(finalCommand)) { | |||
// Try with a property |
@@ -89,6 +89,7 @@ public @interface Property { | |||
* Options for *_LIST types | |||
* | |||
* @since 3.0 Options for property of type PropertyType.SINGLE_SELECT_LIST</code> | |||
* For example {"property_1", "property_3", "property_3"}). | |||
* | |||
* @since 3.3 Options for property of type PropertyType.METRIC</code>. | |||
* If no option is specified, any metric will match. |
@@ -39,7 +39,7 @@ public @interface WidgetProperty { | |||
boolean optional() default true; | |||
/** | |||
* Options for property of type WidgetPropertyType.METRIC</code>. | |||
* @since 3.3 Options for property of type WidgetPropertyType.METRIC</code>. | |||
* | |||
* If no option is specified, any metric will match. | |||
* If options are specified, all must match for the metric to be displayed. | |||
@@ -48,7 +48,9 @@ public @interface WidgetProperty { | |||
* For example <code>type:INT,FLOAT</code> will match any metric of type <code>INT</code> or <code>FLOAT</code>. | |||
* For example <code>type:NUMERIC</code> will match any metric of numerictype. | |||
* | |||
* @since 3.3 | |||
* @since 3.5 Options for property of type WidgetPropertyType.SINGLE_SELECT_LIST</code> | |||
* For example {"property_1", "property_3", "property_3"}). | |||
* | |||
*/ | |||
String[] options() default {}; | |||
} |
@@ -73,5 +73,12 @@ public enum WidgetPropertyType { | |||
* | |||
* @since 3.2 | |||
*/ | |||
REGULAR_EXPRESSION | |||
REGULAR_EXPRESSION, | |||
/** | |||
* Single select list with a list of options | |||
* | |||
* @since 3.5 | |||
*/ | |||
SINGLE_SELECT_LIST | |||
} |
@@ -213,9 +213,4 @@ class ApplicationController < ActionController::Base | |||
message("property.category.#{category}", :default => category) | |||
end | |||
# Force url_to to use relative path | |||
def default_url_options(options) | |||
{:only_path => true} | |||
end | |||
end |
@@ -25,34 +25,56 @@ class BulkDeletionController < ApplicationController | |||
verify :method => :post, :only => [:delete_resources], :redirect_to => { :action => :index } | |||
def index | |||
deletion_manager = ResourceDeletionManager.instance | |||
if pending_mass_deletion? | |||
render :template => 'bulk_deletion/pending_deletions' | |||
return | |||
end | |||
@tabs = deletable_qualifiers | |||
if deletion_manager.currently_deleting_resources? || | |||
(!deletion_manager.currently_deleting_resources? && deletion_manager.deletion_failures_occured?) | |||
# a mass deletion is happening or it has just finished with errors => display the message from the Resource Deletion Manager | |||
@deletion_manager = deletion_manager | |||
@selected_tab = params[:resource_type] | |||
@selected_tab = 'TRK' unless @tabs.include?(@selected_tab) | |||
# Search for resources | |||
conditions = "resource_index.qualifier=:qualifier" | |||
values = {:qualifier => @selected_tab} | |||
if params[:name_filter] && !params[:name_filter].blank? | |||
conditions += " AND resource_index.kee LIKE :kee" | |||
values[:kee] = params[:name_filter].strip.downcase + '%' | |||
end | |||
conditions += " AND projects.enabled=:enabled" | |||
values[:enabled] = true | |||
@resources = Project.find(:all, | |||
:select => 'distinct(resource_index.resource_id),projects.id,projects.name,projects.kee,projects.long_name', | |||
:conditions => [conditions, values], | |||
:joins => :resource_index) | |||
@resources = Api::Utils.insensitive_sort!(@resources){|r| r.name} | |||
end | |||
def ghosts | |||
if pending_mass_deletion? | |||
render :template => 'bulk_deletion/pending_deletions' | |||
else | |||
@tabs = Java::OrgSonarServerUi::JRubyFacade.getInstance().getQualifiersWithProperty('deletable') | |||
@selected_tab = params[:resource_type] | |||
@selected_tab = 'TRK' unless @tabs.include?(@selected_tab) | |||
return | |||
end | |||
# Search for resources | |||
conditions = "resource_index.qualifier=:qualifier" | |||
values = {:qualifier => @selected_tab} | |||
if params[:name_filter] && !params[:name_filter].blank? | |||
conditions += " AND resource_index.kee LIKE :kee" | |||
values[:kee] = params[:name_filter].strip.downcase + '%' | |||
@tabs = deletable_qualifiers | |||
conditions = "scope=:scope AND qualifier IN (:qualifiers) AND status=:status" | |||
values = {:scope => 'PRJ', :qualifiers => @tabs} | |||
unprocessed_project_ids = Snapshot.find(:all, :select => 'project_id', :conditions => [conditions, values.merge({:status => Snapshot::STATUS_UNPROCESSED})]).map(&:project_id).uniq | |||
already_processed_project_ids = Snapshot.find(:all, :select => 'project_id', :conditions => [conditions + " AND project_id IN (:pids)", values.merge({:status => Snapshot::STATUS_PROCESSED, :pids => unprocessed_project_ids})]).map(&:project_id).uniq | |||
@ghosts = Project.find(:all, :conditions => ["id IN (?)", unprocessed_project_ids - already_processed_project_ids]) | |||
@ghosts_by_qualifier = {} | |||
@ghosts.each do |p| | |||
qualifier = p.qualifier | |||
if @ghosts_by_qualifier[qualifier] | |||
@ghosts_by_qualifier[qualifier] << p | |||
else | |||
@ghosts_by_qualifier[qualifier] = [p] | |||
end | |||
conditions += " AND projects.enabled=:enabled" | |||
values[:enabled] = true | |||
@resources = Project.find(:all, | |||
:select => 'distinct(resource_index.resource_id),projects.id,projects.name,projects.kee,projects.long_name', | |||
:conditions => [conditions, values], | |||
:joins => :resource_index) | |||
@resources = Api::Utils.insensitive_sort!(@resources){|r| r.name} | |||
end | |||
end | |||
@@ -86,5 +108,17 @@ class BulkDeletionController < ApplicationController | |||
redirect_to :action => 'index' | |||
end | |||
private | |||
# Tells if a mass deletion is happening or if it has finished with errors | |||
def pending_mass_deletion? | |||
deletion_manager = ResourceDeletionManager.instance | |||
deletion_manager.currently_deleting_resources? || (!deletion_manager.currently_deleting_resources? && deletion_manager.deletion_failures_occured?) | |||
end | |||
def deletable_qualifiers | |||
Java::OrgSonarServerUi::JRubyFacade.getInstance().getQualifiersWithProperty('deletable') | |||
end | |||
end |
@@ -189,11 +189,15 @@ class DashboardController < ApplicationController | |||
end | |||
def load_widget_definitions(filter_on_category) | |||
@widget_definitions=java_facade.getWidgets() | |||
@widget_definitions=java_facade.getWidgets().sort {|w1,w2| widgetL10nName(w1) <=> widgetL10nName(w2)} | |||
@widget_categories=@widget_definitions.map(&:getWidgetCategories).flatten.uniq.sort | |||
unless filter_on_category.blank? | |||
@widget_definitions=@widget_definitions.select { |definition| definition.getWidgetCategories().to_a.include?(filter_on_category) } | |||
end | |||
end | |||
def widgetL10nName(widget) | |||
Api::Utils.message('widget.' + widget.id + '.name') | |||
end | |||
end |
@@ -19,45 +19,81 @@ | |||
# | |||
module PropertiesHelper | |||
# html_options support : | |||
# :html_id - the id of the generated HTML element | |||
SCREEN_SETTINGS = 'SETTINGS' | |||
SCREEN_WIDGET = 'WIDGET' | |||
SCREEN_RULES = 'RULES' | |||
# | |||
# screen is SETTINGS, WIDGET or RULES | |||
# ==== Options | |||
# * <tt>:id</tt> - html id to use if the name should not be used | |||
# * <tt>:size</tt> | |||
# * <tt>:values</tt> - list of values for metric and single select list | |||
# * <tt>:default</tt> - default value | |||
# * <tt>:disabled</tt> | |||
# * <tt>:extra_values</tt> | |||
# | |||
def property_input_field(key, type, value, options, html_options = {}) | |||
if type==PropertyType::TYPE_STRING | |||
text_field_tag key, value, {:size => 25}.update(html_options) | |||
def property_input_field(name, type, value, screen, options = {}) | |||
elsif type==PropertyType::TYPE_TEXT | |||
text_area_tag key, value, {:rows => '6', :style => 'width: 100%'}.update(html_options) | |||
html_options = {:id => options[:id] || name} | |||
html_options[:disabled] = 'disabled' if options[:disabled] | |||
elsif type==PropertyType::TYPE_PASSWORD | |||
password_field_tag key, value, {:size => 25}.update(html_options) | |||
case type | |||
elsif type==PropertyType::TYPE_BOOLEAN | |||
(hidden_field_tag key, 'false', html_options) + (check_box_tag key, 'true', value=='true', html_options) | |||
when PropertyType::TYPE_STRING | |||
text_field_tag name, value, {:size => options[:size] || 50}.update(html_options) | |||
elsif type==PropertyType::TYPE_INTEGER | |||
text_field_tag key, value, {:size => 10}.update(html_options) | |||
when PropertyType::TYPE_TEXT | |||
cols = options[:size] || nil | |||
html_class = cols.nil? ? ' width100' : '' | |||
text_area_tag name, value, {:class => html_class, :rows => '5', :cols => cols}.update(html_options) | |||
elsif type==PropertyType::TYPE_FLOAT | |||
text_field_tag key, value, {:size => 10}.update(html_options) | |||
when PropertyType::TYPE_PASSWORD | |||
password_field_tag name, value, {:size => options[:size] || 50}.update(html_options) | |||
elsif type==PropertyType::TYPE_METRIC | |||
metric_select_tag key, metrics_filtered_by(options), {:selected_key => value, :allow_empty => true}.update(html_options) | |||
when PropertyType::TYPE_BOOLEAN | |||
select_options = "<option value='' #{ 'selected' if value.blank? }>#{ message('default') }</option>" | |||
select_options += "<option value='true' #{ 'selected' if value=='true' }>#{ message('true') }</option>" | |||
select_options += "<option value='false' #{ 'selected' if value=='false' }>#{ message('false') }</option>" | |||
select_tag name, select_options, html_options | |||
elsif type==PropertyType::TYPE_REGULAR_EXPRESSION | |||
text_field_tag key, value, {:size => 25}.update(html_options) | |||
when PropertyType::TYPE_INTEGER | |||
size = options[:size] || 10 | |||
text_field_tag name, value, {:size => size}.update(html_options) | |||
elsif type==PropertyType::TYPE_FILTER | |||
user_filters = options_id(value, current_user.measure_filters) | |||
shared_filters = options_id(value, MeasureFilter.find(:all, :conditions => ['(user_id<>? or user_id is null) and shared=?', current_user.id, true]).sort_by(&:name)) | |||
when PropertyType::TYPE_FLOAT | |||
size = options[:size] || 10 | |||
text_field_tag name, value, {:size => size}.update(html_options) | |||
filters_combo = select_tag key, option_group('My Filters', user_filters) + option_group('Shared Filters', shared_filters), html_options | |||
filter_link = link_to message('widget.filter.edit'), {:controller => 'measures', :action => 'manage'}, :class => 'link-action' | |||
when PropertyType::TYPE_METRIC | |||
metric_select_tag name, metrics_filtered_by(options[:values]), {:html_id => options[:id], :selected_key => value, :allow_empty => true, | |||
:placeholder => !options[:default].blank? ? message('default') : nil} | |||
"#{filters_combo} #{filter_link}" | |||
when PropertyType::TYPE_REGULAR_EXPRESSION | |||
size = options[:size] || 50 | |||
text_field_tag name, value, {:size => size}.update(html_options) | |||
else | |||
hidden_field_tag key, html_options | |||
when PropertyType::TYPE_FILTER | |||
user_filters = options_id(value, current_user.measure_filters) | |||
shared_filters = options_id(value, MeasureFilter.find(:all, :conditions => ['(user_id<>? or user_id is null) and shared=?', current_user.id, true]).sort_by(&:name)) | |||
filters_combo = select_tag name, option_group('My Filters', user_filters) + option_group('Shared Filters', shared_filters), html_options | |||
filter_link = link_to message('widget.filter.edit'), {:controller => 'measures', :action => 'manage'}, :class => 'link-action' | |||
"#{filters_combo} #{filter_link}" | |||
when PropertyType::TYPE_SINGLE_SELECT_LIST | |||
default_value = options[:default].blank? ? '' : message('default') | |||
select_options = "<option value=''>#{ default_value }</option>" | |||
options[:values].each do |option| | |||
message = screen == SCREEN_WIDGET ? option_name_with_key(name, nil, option, 'widget.'+ options[:extra_values][:widget_key]) : | |||
option_name(options[:extra_values][:property], options[:extra_values][:field], option) | |||
select_options += "<option value='#{ html_escape option }' #{ 'selected' if value && value==option }>#{ message }</option>" | |||
end | |||
select_tag name, select_options, html_options | |||
else | |||
hidden_field_tag id, html_options | |||
end | |||
end | |||
@@ -92,4 +128,22 @@ module PropertiesHelper | |||
end | |||
end | |||
def option_name_with_key(property_key, field_key, option, key_prefix = '') | |||
prefix = '' | |||
prefix = key_prefix + "." if !key_prefix.blank? | |||
if field_key | |||
# Old key used for retro-compatibility | |||
message = message(prefix +"option.#{property_key}.#{field_key}.#{option}.name", :default => '') | |||
message = message(prefix +"property.#{property_key}.#{field_key}.option.#{option}.name", :default => option) unless message != '' | |||
message | |||
else | |||
# Old key used for retro-compatibility | |||
puts "#### "+ prefix.to_s | |||
message = message(prefix +"option.#{property_key}.#{option}.name", :default => '') | |||
message = message(prefix +"property.#{property_key}.option.#{option}.name", :default => option) unless message != '' | |||
message | |||
end | |||
end | |||
end |
@@ -49,8 +49,8 @@ module RulesConfigurationHelper | |||
def param_value_input(parameter, value, options = {}) | |||
type=type_with_compatibility(parameter.param_type) | |||
property_input_field 'value', type, value, nil, {:id => parameter.id}.update(options) | |||
name = options[:name] || 'value' | |||
property_input_field name, type, value, 'WIDGET', {:id => parameter.id, :size => options[:size] }.update(options) | |||
end | |||
def is_set(type) |
@@ -45,11 +45,7 @@ module SettingsHelper | |||
end | |||
def option_name(property, field, option) | |||
if field | |||
message("option.#{property.key}.#{field.key}.#{option}.name", :default => option) | |||
else | |||
message("option.#{property.key}.#{option}.name", :default => option) | |||
end | |||
option_name_with_key(property.key, field ? field.key : nil, option, nil) | |||
end | |||
def category_help(category) |
@@ -21,8 +21,18 @@ module WidgetPropertiesHelper | |||
include PropertiesHelper | |||
def property_value_field(definition, value, widget) | |||
property_input_field definition.key, definition.type.name, value.nil? ? definition.defaultValue : value, definition.options, | |||
{:html_id => "prop-#{widget.id}-#{widget.key.parameterize}-#{definition.key.parameterize}"} | |||
id = definition.type.name != PropertyType::TYPE_METRIC ? definition.key : "prop-#{widget.id}-#{widget.key.parameterize}-#{definition.key.parameterize}" | |||
options = {:values => definition.options, :id => id, :default => definition.defaultValue} | |||
options[:extra_values] = {:widget_key => widget.key} if definition.type.name == PropertyType::TYPE_SINGLE_SELECT_LIST | |||
property_input_field definition.key, definition.type.name, value, 'WIDGET', options | |||
end | |||
def default_value(property_def) | |||
defaultValue = property_def.defaultValue | |||
# Boolean type should always have a default value, if no one is provided it's force to false | |||
defaultValue = property_def.type.name == PropertyType::TYPE_BOOLEAN ? 'false' : property_def.defaultValue if defaultValue.blank? | |||
defaultValue = '********' if property_def.type.name == PropertyType::TYPE_PASSWORD | |||
defaultValue | |||
end | |||
end |
@@ -24,7 +24,7 @@ class PropertyType | |||
TYPE_BOOLEAN = 'BOOLEAN' | |||
TYPE_INTEGER = 'INTEGER' | |||
TYPE_FLOAT = 'FLOAT' | |||
#TYPE_SINGLE_SELECT_LIST = 'SINGLE_SELECT_LIST' | |||
TYPE_SINGLE_SELECT_LIST = 'SINGLE_SELECT_LIST' | |||
TYPE_METRIC = 'METRIC' | |||
TYPE_LICENSE = 'LICENSE' | |||
TYPE_REGULAR_EXPRESSION = 'REGULAR_EXPRESSION' |
@@ -0,0 +1,65 @@ | |||
<h1 class="marginbottom10"><%= message('bulk_deletion.page') -%></h1> | |||
<ul class="tabs"> | |||
<% @tabs.each do |tab| %> | |||
<li> | |||
<a href="<%= url_for :action => 'index', :resource_type => tab %>" id="tab-<%= u tab -%>"><%= message('qualifiers.' + tab) -%></a> | |||
</li> | |||
<% end %> | |||
<li> | |||
<a href="<%= url_for :action => 'ghosts' %>" id="tab-ghosts" class="selected"><%= message('bulk_deletion.ghosts') -%></a> | |||
</li> | |||
</ul> | |||
<div class="tabs-panel marginbottom10"> | |||
<p style="margin: 0 0 15px 0"> | |||
<%= message('bulk_deletion.ghosts.description') -%> | |||
</p> | |||
<% unless @ghosts.size > 0 %> | |||
<b><%= message('bulk_deletion.no_ghosts') -%></b> | |||
<% else %> | |||
<p style="margin: 0 0 10px 0"> | |||
<%= message('bulk_deletion.following_ghosts_can_be_deleted') -%> | |||
</p> | |||
<table> | |||
<% | |||
@tabs.each do |qualifier| | |||
ghosts = @ghosts_by_qualifier[qualifier] | |||
if ghosts | |||
%> | |||
<tr> | |||
<td class="thin nowrap top" style="padding-right: 20px"> | |||
<b><%= message('qualifiers.' + qualifier) -%> :</b> | |||
</td> | |||
<td> | |||
<ul> | |||
<% | |||
ghosts.sort {|x,y| x.name <=> y.name}.each_with_index do |resource, index| | |||
%> | |||
<li><%= h resource.name -%> <span class="small gray">( <%= resource.key -%> )</span></li> | |||
<% | |||
end | |||
%> | |||
</ul> | |||
<% | |||
end | |||
end | |||
%> | |||
</table> | |||
<p style="margin: 15px 0"> | |||
<% form_remote_tag( :url => {:action => 'delete_resources'}, :loading => "window.location='#{url_for :action => 'pending_deletions'}';") do %> | |||
<input type="hidden" id="all_resources" name="all_resources" value="<%= @ghosts.map {|r| r.id.to_s}.join(',') -%>"/> | |||
<input id="delete_resources" class="action red-button" type="submit" value="<%= message('bulk_deletion.delete_all_ghosts') -%>" name="commit"> | |||
<% end %> | |||
</p> | |||
<% end %> | |||
</div> |
@@ -6,6 +6,9 @@ | |||
<a href="<%= url_for :action => 'index', :resource_type => tab %>" <%= "class='selected'" if @selected_tab==tab -%> id="tab-<%= u tab -%>"><%= message('qualifiers.' + tab) -%></a> | |||
</li> | |||
<% end %> | |||
<li> | |||
<a href="<%= url_for :action => 'ghosts' %>" id="tab-ghosts"><%= message('bulk_deletion.ghosts') -%></a> | |||
</li> | |||
</ul> | |||
<div class="tabs-panel marginbottom10"> |
@@ -24,15 +24,22 @@ | |||
<% widget.java_definition.getWidgetProperties().each do |property_def| %> | |||
<tr> | |||
<td class="form-key-cell"><%= message("widget." + widget.key + ".prop." + property_def.key() + ".key", :default => property_def.key()) -%><%= " *" unless property_def.optional() -%></td> | |||
<td class="form-key-cell"><%= message("widget." + widget.key + ".property." + property_def.key() + ".name", :default => property_def.key()) -%><%= " *" unless property_def.optional() -%></td> | |||
<td class="form-val-cell" id="row_<%= property_def.key() -%>"> | |||
<%= property_value_field(property_def, widget.property_text_value(property_def.key()), widget) -%> | |||
<div class="form-val-note"> | |||
<% | |||
# Old key used for retro-compatibility | |||
property_description = message("widget." + widget.key + ".param." + property_def.key(), :default => '') | |||
property_description = message("widget." + widget.key + ".prop." + property_def.key() + ".desc", :default => property_def.description()) unless property_description != '' | |||
property_description = message("widget." + widget.key + ".property." + property_def.key() + ".desc", :default => property_def.description()) unless property_description != '' | |||
-%> | |||
<%= property_description -%> | |||
<% unless property_description.blank? -%> | |||
<div><%= property_description -%></div> | |||
<% end %> | |||
<% if !property_def.defaultValue.blank? || property_def.type.name == PropertyType::TYPE_BOOLEAN -%> | |||
<div><%= message('default') %>: <%= h(default_value property_def) -%></div> | |||
<% end -%> | |||
</div> | |||
</td> | |||
</tr> |
@@ -19,7 +19,7 @@ | |||
url += '?sort=' + columnName; | |||
} | |||
if (!document.URL.contains('&asc=true')) { | |||
if (document.URL.indexOf('&asc=true') == -1) { | |||
url += '&asc=true'; | |||
} | |||
} |
@@ -44,7 +44,7 @@ | |||
<% default_prop_value = (@resource ? Property.value(property.key, nil, property.defaultValue) : property.defaultValue) -%> | |||
<% unless default_prop_value.blank? -%> | |||
<div class="note">Default: <%= property.type.to_s=='PASSWORD' ? '********' : h(default_prop_value) -%></div> | |||
<div class="note"><%= message('default') %>: <%= property.type.to_s=='PASSWORD' ? '********' : h(default_prop_value) -%></div> | |||
<% end -%> | |||
</td> | |||
</tr> |
@@ -1,5 +1 @@ | |||
<select name="<%= name -%>" id="<%= id -%>"> | |||
<option value="" <%= 'selected' if value.blank? -%>><%= message('default') -%></option> | |||
<option value="true" <%= 'selected' if value=='true' -%>><%= message('true') -%></option> | |||
<option value="false" <%= 'selected' if value=='false' -%>><%= message('false') -%></option> | |||
</select> | |||
<%= property_input_field(name, PropertyType::TYPE_BOOLEAN, value, PropertiesHelper::SCREEN_SETTINGS, {:id => id, :default => (defined? property.defaultValue) ? property.defaultValue : nil }) %> |
@@ -1 +1,5 @@ | |||
<input type="text" name="<%= name -%>" value="<%= h value if value -%>" size="<%= (defined? size) ? size : 50 -%>" id="<%= id -%>"/> | |||
<% | |||
options = {:id => id} | |||
options[:size] = (defined? size) ? size : nil | |||
%> | |||
<%= property_input_field(name, PropertyType::TYPE_FLOAT, value, PropertiesHelper::SCREEN_SETTINGS, options) %> |
@@ -1 +1,5 @@ | |||
<input type="text" name="<%= name -%>" value="<%= h value if value -%>" size="<%= (defined? size) ? size : 50 -%>" id="<%= id-%>"/> | |||
<% | |||
options = {:id => id} | |||
options[:size] = (defined? size) ? size : nil | |||
%> | |||
<%= property_input_field(name, PropertyType::TYPE_INTEGER, value, PropertiesHelper::SCREEN_SETTINGS, options) %> |
@@ -1,5 +1,8 @@ | |||
<% | |||
defaultValue = (defined? property.defaultValue) ? property.defaultValue : nil | |||
%> | |||
<select name="<%= name -%>" id="<%= id -%>"> | |||
<option value=""><%= message('default') -%></option> | |||
<option value=""><%= !defaultValue.blank? ? message('default') : nil -%></option> | |||
<% | |||
metrics_per_domain={} | |||
metrics_filtered_by(property.options).each do |metric| |
@@ -1 +1,5 @@ | |||
<input type="password" name="<%= name -%>" value="<%= h value if value -%>" size="<%= defined? size ? size : 50-%>" id="<%= id -%>"/> | |||
<% | |||
options = {:id => id} | |||
options[:size] = (defined? size) ? size : nil | |||
%> | |||
<%= property_input_field(name, PropertyType::TYPE_PASSWORD, value, PropertiesHelper::SCREEN_SETTINGS, options) %> |
@@ -1 +1,5 @@ | |||
<input type="text" name="<%= name -%>" value="<%= h value if value -%>" size="<%= (defined? size) ? size : 50 -%>" id="<%= id -%>"/> | |||
<% | |||
options = {:id => id} | |||
options[:size] = (defined? size) ? size : nil | |||
%> | |||
<%= property_input_field(name, PropertyType::TYPE_REGULAR_EXPRESSION, value, PropertiesHelper::SCREEN_SETTINGS, options) %> |
@@ -1,6 +1,2 @@ | |||
<select name="<%= name -%>" id="<%= id -%>"> | |||
<option value=""><%= message('default') -%></option> | |||
<% property.options.each do |option| %> | |||
<option value="<%= h option -%>" <%= 'selected' if value && value==option -%>><%= option_name(property, field, option) -%></option> | |||
<% end %> | |||
</select> | |||
<%= property_input_field(name, PropertyType::TYPE_SINGLE_SELECT_LIST, value, PropertiesHelper::SCREEN_SETTINGS, | |||
{:id => id, :default => (defined? property.defaultValue) ? property.defaultValue : nil, :values => property.options, :extra_values => {:property => property, :field => field}}) %> |
@@ -1 +1,5 @@ | |||
<input type="text" name="<%= name -%>" value="<%= h value if value -%>" size="<%= (defined? size) ? size : 50 -%>" id="<%= id -%>"/> | |||
<% | |||
options = {:id => id} | |||
options[:size] = (defined? size) ? size : nil | |||
%> | |||
<%= property_input_field(name, PropertyType::TYPE_STRING, value, PropertiesHelper::SCREEN_SETTINGS, options) %> |
@@ -1 +1,5 @@ | |||
<textarea rows="5" cols="<%= (defined? size) ? size : 80 -%>" class="<%= (defined? size) ? '' : 'width100' -%>" name="<%= name -%>" id="<%= id -%>"><%= h value if value -%></textarea> | |||
<% | |||
options = {:id => id} | |||
options[:size] = (defined? size) ? size : nil | |||
%> | |||
<%= property_input_field(name, PropertyType::TYPE_TEXT, value, PropertiesHelper::SCREEN_SETTINGS, options) %> |
@@ -30,7 +30,7 @@ ActionController::Routing::Routes.draw do |map| | |||
map.resources 'properties', :path_prefix => 'api', :controller => 'api/properties', :requirements => { :id => /.*/ } | |||
# home page | |||
map.home '', :controller => :dashboard, :action => :index, :only_path => true | |||
map.home '', :controller => :dashboard, :action => :index | |||
map.root :controller => :dashboard, :action => :index | |||
# page plugins |