diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2013-01-25 14:04:03 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2013-01-25 14:04:03 +0100 |
commit | e344cab899a24d7d5a01cb0e39de8e7983c704b9 (patch) | |
tree | 19232b5b69de252f320b45488dda242a418e4267 | |
parent | 6200a323e1a2af7725ec9eb78dd9ca0902f3db38 (diff) | |
parent | 3c4b6fb0ef02390e1683587d96f17a408edc6d06 (diff) | |
download | sonarqube-e344cab899a24d7d5a01cb0e39de8e7983c704b9.tar.gz sonarqube-e344cab899a24d7d5a01cb0e39de8e7983c704b9.zip |
Merge remote-tracking branch 'origin/master'
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); + } + +} @@ -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 |