aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2013-01-25 14:04:03 +0100
committerSimon Brandhof <simon.brandhof@gmail.com>2013-01-25 14:04:03 +0100
commite344cab899a24d7d5a01cb0e39de8e7983c704b9 (patch)
tree19232b5b69de252f320b45488dda242a418e4267
parent6200a323e1a2af7725ec9eb78dd9ca0902f3db38 (diff)
parent3c4b6fb0ef02390e1683587d96f17a408edc6d06 (diff)
downloadsonarqube-e344cab899a24d7d5a01cb0e39de8e7983c704b9.tar.gz
sonarqube-e344cab899a24d7d5a01cb0e39de8e7983c704b9.zip
Merge remote-tracking branch 'origin/master'
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java732
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java51
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java11
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java12
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TreemapWidget.java8
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/FalsePositiveReviewsWidget.java5
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/MyReviewsWidget.java5
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/PlannedReviewsWidget.java5
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ProjectReviewsWidget.java5
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/UnplannedReviewsWidget.java5
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties85
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/action_plans.html.erb6
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/GenerateAlertEventsTest.java58
-rw-r--r--plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/EmailNotificationsPlugin.java14
-rw-r--r--plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/alerts/AlertsEmailTemplate.java107
-rw-r--r--plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/alerts/AlertsOnMyFavouriteProject.java53
-rw-r--r--plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/EmailNotificationsPluginTest.java2
-rw-r--r--plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/alerts/AlertsEmailTemplateTest.java131
-rw-r--r--plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/alerts/AlertsOnMyFavouriteProjectTest.java62
-rw-r--r--pom.xml2
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java4
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java8
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskBootstrapModule.java4
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java4
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/Property.java1
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetProperty.java6
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java9
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb5
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb82
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb8
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb108
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/rules_configuration_helper.rb4
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/settings_helper.rb6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/widget_properties_helper.rb14
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/ghosts.html.erb65
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb3
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb13
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/project_reviews/index.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb5
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb8
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/config/routes.rb2
50 files changed, 1242 insertions, 524 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index 652ea938ff4..bd7015dfddf 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -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);
}
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java
index 4abe16d4262..3e4d6150920 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java
@@ -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);
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java
index 6551fcc96ab..3a97dc4d7be 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java
@@ -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");
}
-} \ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java
index 25d19a9f740..a7f2e322d81 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java
@@ -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() {
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TreemapWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TreemapWidget.java
index 8ac21eaa09d..f9844bfbe06 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TreemapWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TreemapWidget.java
@@ -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");
}
-} \ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/FalsePositiveReviewsWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/FalsePositiveReviewsWidget.java
index 514f2c75f39..c18e368c9db 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/FalsePositiveReviewsWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/FalsePositiveReviewsWidget.java
@@ -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");
}
-} \ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/MyReviewsWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/MyReviewsWidget.java
index 64d6828c5d2..de2b592de7b 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/MyReviewsWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/MyReviewsWidget.java
@@ -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");
}
-} \ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/PlannedReviewsWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/PlannedReviewsWidget.java
index 094f3bba94a..01972c61d1b 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/PlannedReviewsWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/PlannedReviewsWidget.java
@@ -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");
}
-} \ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ProjectReviewsWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ProjectReviewsWidget.java
index 0dc6a863479..2d69c8fc68c 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ProjectReviewsWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ProjectReviewsWidget.java
@@ -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");
}
-} \ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/UnplannedReviewsWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/UnplannedReviewsWidget.java
index 5b88866befb..2c866593971 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/UnplannedReviewsWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/UnplannedReviewsWidget.java
@@ -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");
}
-} \ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
index 046b45a49ac..ba45487df1e 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
@@ -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
#------------------------------------------------------------------------------
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/action_plans.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/action_plans.html.erb
index e6de5f33999..bbd2b41f3c4 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/action_plans.html.erb
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/action_plans.html.erb
@@ -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 %>
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/GenerateAlertEventsTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/GenerateAlertEventsTest.java
index ddbd9cc18e4..994bb282a70 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/GenerateAlertEventsTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/GenerateAlertEventsTest.java
@@ -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));
}
@@ -74,17 +87,29 @@ public class GenerateAlertEventsTest {
}
@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));
+ }
}
diff --git a/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/EmailNotificationsPlugin.java b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/EmailNotificationsPlugin.java
index bf46289eb47..61051c38e10 100644
--- a/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/EmailNotificationsPlugin.java
+++ b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/EmailNotificationsPlugin.java
@@ -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
+ );
}
}
diff --git a/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/alerts/AlertsEmailTemplate.java b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/alerts/AlertsEmailTemplate.java
new file mode 100644
index 00000000000..96d96cb3ad7
--- /dev/null
+++ b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/alerts/AlertsEmailTemplate.java
@@ -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();
+ }
+
+}
diff --git a/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/alerts/AlertsOnMyFavouriteProject.java b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/alerts/AlertsOnMyFavouriteProject.java
new file mode 100644
index 00000000000..38a46a6ca49
--- /dev/null
+++ b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/alerts/AlertsOnMyFavouriteProject.java
@@ -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);
+ }
+ }
+ }
+
+}
diff --git a/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/EmailNotificationsPluginTest.java b/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/EmailNotificationsPluginTest.java
index eceb69f392e..aeb4755d940 100644
--- a/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/EmailNotificationsPluginTest.java
+++ b/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/EmailNotificationsPluginTest.java
@@ -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);
}
}
diff --git a/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/alerts/AlertsEmailTemplateTest.java b/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/alerts/AlertsEmailTemplateTest.java
new file mode 100644
index 00000000000..1a68497f1c9
--- /dev/null
+++ b/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/alerts/AlertsEmailTemplateTest.java
@@ -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;
+ }
+
+}
diff --git a/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/alerts/AlertsOnMyFavouriteProjectTest.java b/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/alerts/AlertsOnMyFavouriteProjectTest.java
new file mode 100644
index 00000000000..fe29748d332
--- /dev/null
+++ b/plugins/sonar-email-notifications-plugin/src/test/java/org/sonar/plugins/emailnotifications/alerts/AlertsOnMyFavouriteProjectTest.java
@@ -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);
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index b45669c07c3..0729368fb6a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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>
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java
index 0987d020216..0131e8b59e9 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java
@@ -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");
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
index 6e2d80571c6..c65099b3f22 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
@@ -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;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskBootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskBootstrapModule.java
index 052d9a07931..5db5fcf3454 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskBootstrapModule.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskBootstrapModule.java
@@ -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;
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java b/sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java
index 8e5b8698eb4..9ff56cdd9ba 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/tasks/Tasks.java
@@ -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
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/Property.java b/sonar-plugin-api/src/main/java/org/sonar/api/Property.java
index 4802b6fe251..8d3a370ad15 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/Property.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/Property.java
@@ -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.
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetProperty.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetProperty.java
index 90fe3ea39fe..f646a7822f9 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetProperty.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetProperty.java
@@ -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 {};
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java
index edff6ee319f..2b64edfd085 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java
@@ -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
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
index 2f765f95f1b..58bc66a29e5 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb
index 7697ea9be2f..636ee723424 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb
index 5d570c4964a..8ad90815c07 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb
index 2524b27bf20..a63afaf7b96 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/rules_configuration_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/rules_configuration_helper.rb
index 8f797bcd9fd..3ec0ee1aafd 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/rules_configuration_helper.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/rules_configuration_helper.rb
@@ -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)
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/settings_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/settings_helper.rb
index f019fdc4ff1..e61180f519c 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/settings_helper.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/settings_helper.rb
@@ -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)
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/widget_properties_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/widget_properties_helper.rb
index a6e161cecdb..ccada016ab8 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/widget_properties_helper.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/widget_properties_helper.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb
index 8fcb9f06da3..0ef74a897b8 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb
@@ -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'
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/ghosts.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/ghosts.html.erb
new file mode 100644
index 00000000000..77a13a6029e
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/ghosts.html.erb
@@ -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> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb
index 70ccdecf0d0..1341d8b1f3c 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb
@@ -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">
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb
index 8b1942a59be..ae05758e1ca 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb
@@ -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>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project_reviews/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project_reviews/index.html.erb
index 25c95cb71dc..0b3d3f249ba 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/project_reviews/index.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project_reviews/index.html.erb
@@ -19,7 +19,7 @@
url += '?sort=' + columnName;
}
- if (!document.URL.contains('&asc=true')) {
+ if (document.URL.indexOf('&asc=true') == -1) {
url += '&asc=true';
}
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb
index fed0c43bc2b..f18db1161ff 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb
@@ -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>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb
index 117cf3b9127..74e6c8c8635 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb
@@ -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> \ No newline at end of file
+<%= property_input_field(name, PropertyType::TYPE_BOOLEAN, value, PropertiesHelper::SCREEN_SETTINGS, {:id => id, :default => (defined? property.defaultValue) ? property.defaultValue : nil }) %> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb
index 80bad336880..721480a62aa 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb
@@ -1 +1,5 @@
-<input type="text" name="<%= name -%>" value="<%= h value if value -%>" size="<%= (defined? size) ? size : 50 -%>" id="<%= id -%>"/> \ No newline at end of file
+<%
+ options = {:id => id}
+ options[:size] = (defined? size) ? size : nil
+%>
+<%= property_input_field(name, PropertyType::TYPE_FLOAT, value, PropertiesHelper::SCREEN_SETTINGS, options) %> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb
index ea7d4d7d8e7..5f7c39e8c7b 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb
@@ -1 +1,5 @@
-<input type="text" name="<%= name -%>" value="<%= h value if value -%>" size="<%= (defined? size) ? size : 50 -%>" id="<%= id-%>"/> \ No newline at end of file
+<%
+ options = {:id => id}
+ options[:size] = (defined? size) ? size : nil
+%>
+<%= property_input_field(name, PropertyType::TYPE_INTEGER, value, PropertiesHelper::SCREEN_SETTINGS, options) %> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb
index d06c87e3272..18e706b7f26 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb
@@ -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|
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb
index 919ff05bd61..68bc7edb71a 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb
@@ -1 +1,5 @@
-<input type="password" name="<%= name -%>" value="<%= h value if value -%>" size="<%= defined? size ? size : 50-%>" id="<%= id -%>"/> \ No newline at end of file
+<%
+ options = {:id => id}
+ options[:size] = (defined? size) ? size : nil
+%>
+<%= property_input_field(name, PropertyType::TYPE_PASSWORD, value, PropertiesHelper::SCREEN_SETTINGS, options) %> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb
index 80bad336880..8274fd8ef8f 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb
@@ -1 +1,5 @@
-<input type="text" name="<%= name -%>" value="<%= h value if value -%>" size="<%= (defined? size) ? size : 50 -%>" id="<%= id -%>"/> \ No newline at end of file
+<%
+ options = {:id => id}
+ options[:size] = (defined? size) ? size : nil
+%>
+<%= property_input_field(name, PropertyType::TYPE_REGULAR_EXPRESSION, value, PropertiesHelper::SCREEN_SETTINGS, options) %> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
index 2737ec732fc..4153c332d13 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
@@ -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> \ No newline at end of file
+<%= 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}}) %> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb
index 80bad336880..b28c75604a7 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb
@@ -1 +1,5 @@
-<input type="text" name="<%= name -%>" value="<%= h value if value -%>" size="<%= (defined? size) ? size : 50 -%>" id="<%= id -%>"/> \ No newline at end of file
+<%
+ options = {:id => id}
+ options[:size] = (defined? size) ? size : nil
+%>
+<%= property_input_field(name, PropertyType::TYPE_STRING, value, PropertiesHelper::SCREEN_SETTINGS, options) %> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb
index 414175775dc..af46cf40082 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb
@@ -1 +1,5 @@
-<textarea rows="5" cols="<%= (defined? size) ? size : 80 -%>" class="<%= (defined? size) ? '' : 'width100' -%>" name="<%= name -%>" id="<%= id -%>"><%= h value if value -%></textarea> \ No newline at end of file
+<%
+ options = {:id => id}
+ options[:size] = (defined? size) ? size : nil
+%>
+<%= property_input_field(name, PropertyType::TYPE_TEXT, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/config/routes.rb b/sonar-server/src/main/webapp/WEB-INF/config/routes.rb
index 642c8d9e340..6cfed6caea7 100644
--- a/sonar-server/src/main/webapp/WEB-INF/config/routes.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/config/routes.rb
@@ -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