diff options
195 files changed, 2805 insertions, 1026 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb index 000f9c35b81..0e645dfa2bd 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb @@ -43,12 +43,14 @@ new_technical_debt_variation = variation_value(new_technical_debt) estimated_cleared_technical_debt = (new_technical_debt_variation - technical_debt_variation).to_i if technical_debt_variation && new_technical_debt_variation %> - <p class="small"> - <%= message('widget.rules.added') %> - <a href="<%= url_for_drilldown('new_technical_debt', :period => @dashboard_configuration.period_index) -%>" - class="varw widget-link widget-link-red link-<%= widget.key %>-new-debt" - title="<%= tooltip -%>" data-toggle="tooltip" data-placement="bottom">+<%= format_variation(new_technical_debt, :style => 'none', :default => '-') -%></a> - </p> + <% if new_technical_debt_variation && new_technical_debt_variation > 0 %> + <p class="small"> + <%= message('widget.rules.added') %> + <a href="<%= url_for_drilldown('new_technical_debt', :period => @dashboard_configuration.period_index) -%>" + class="varw widget-link widget-link-red link-<%= widget.key %>-new-debt" + title="<%= tooltip -%>" data-toggle="tooltip" data-placement="bottom">+<%= format_variation(new_technical_debt, :style => 'none', :default => '-') -%></a> + </p> + <% end %> <% if estimated_cleared_technical_debt && estimated_cleared_technical_debt > 0 %> <p class="small"> <%= message('widget.rules.removed') %> @@ -82,12 +84,14 @@ new_issues_variation = variation_value(new_issues) estimated_cleared_issues = (new_issues_variation - issues_variation).to_i if issues_variation && new_issues_variation %> - <p class="small"> - <%= message('widget.rules.added') %> - <a href="<%= url_for(:controller => 'component_issues', :action => 'index') -%>?id=<%= url_encode(@project.key) -%>#resolved=false|createdAfter=<%= period_date -%>" - class="varw widget-link widget-link-red link-<%= widget.key %>-new-issues" - title="<%= tooltip -%>" data-toggle="tooltip" data-placement="bottom">+<%= format_variation(new_issues, :style => 'none', :default => '-') -%></a> - </p> + <% if new_issues_variation && new_issues_variation > 0 %> + <p class="small"> + <%= message('widget.rules.added') %> + <a href="<%= url_for(:controller => 'component_issues', :action => 'index') -%>?id=<%= url_encode(@project.key) -%>#resolved=false|createdAfter=<%= period_date -%>" + class="varw widget-link widget-link-red link-<%= widget.key %>-new-issues" + title="<%= tooltip -%>" data-toggle="tooltip" data-placement="bottom">+<%= format_variation(new_issues, :style => 'none', :default => '-') -%></a> + </p> + <% end %> <% if estimated_cleared_issues && estimated_cleared_issues > 0 %> <p class="small"> <%= message('widget.rules.removed') %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb index 18c5d7785a2..55ce78fe4d6 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb @@ -101,7 +101,7 @@ from_date = first_date if !from_date || from_date > first_date end end - Event.find(:all, :conditions => ["resource_id=? AND event_date>=?", @resource.id, from_date], :order => 'event_date').each() do |event| + Event.find(:all, :conditions => ["resource_id=? AND event_date>=?", @resource.id, from_date.to_i*1000], :order => 'event_date').each() do |event| if events[event.event_date] events[event.event_date] << event else diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectRepositoryLoader.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectRepositoryLoader.java index 5c265dfc5e7..4b8ce4c3b48 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectRepositoryLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectRepositoryLoader.java @@ -20,6 +20,7 @@ package org.sonar.server.batch; +import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; @@ -67,7 +68,7 @@ public class ProjectRepositoryLoader implements ServerComponent { private final Languages languages; public ProjectRepositoryLoader(DbClient dbClient, QProfileFactory qProfileFactory, QProfileLoader qProfileLoader, RuleService ruleService, - Languages languages) { + Languages languages) { this.dbClient = dbClient; this.qProfileFactory = qProfileFactory; this.qProfileLoader = qProfileLoader; @@ -86,10 +87,9 @@ public class ProjectRepositoryLoader implements ServerComponent { ComponentDto module = dbClient.componentDao().getNullableByKey(session, query.getModuleKey()); // Current project/module can be null when analysing a new project if (module != null) { - if (query.isPreview()) { - // Scan permission is enough to analyze all projects but preview permission is limited to projects user can access - UserSession.get().checkComponentPermission(UserRole.USER, query.getModuleKey(), - "You're not authorized to access to project '" + module.name() + "', please contact your SonarQube administrator."); + // Scan permission is enough to analyze all projects but preview permission is limited to projects user can access + if (query.isPreview() && !UserSession.get().hasProjectPermissionByUuid(UserRole.USER, module.projectUuid())) { + throw new ForbiddenException("You're not authorized to access to project '" + module.name() + "', please contact your SonarQube administrator."); } ComponentDto project = getProject(module, session); @@ -156,7 +156,7 @@ public class ProjectRepositoryLoader implements ServerComponent { } private void addSettingsToChildrenModules(ProjectRepositories ref, String moduleKey, Map<String, String> parentProperties, TreeModuleSettings treeModuleSettings, - boolean hasScanPerm, DbSession session) { + boolean hasScanPerm, DbSession session) { Map<String, String> currentParentProperties = newHashMap(); currentParentProperties.putAll(parentProperties); currentParentProperties.putAll(getPropertiesMap(treeModuleSettings.findModuleSettings(moduleKey), hasScanPerm)); @@ -224,12 +224,15 @@ public class ProjectRepositoryLoader implements ServerComponent { private void addActiveRules(ProjectRepositories ref) { for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) { - for (ActiveRule activeRule : qProfileLoader.findActiveRulesByProfile(qProfile.key())) { - Rule rule = ruleService.getNonNullByKey(activeRule.key().ruleKey()); + Map<RuleKey, ActiveRule> activeRules = activeRuleByRuleKey(qProfileLoader.findActiveRulesByProfile(qProfile.key())); + Iterator<Rule> rules = ruleService.search(new RuleQuery().setQProfileKey(qProfile.key()).setActivation(true), new QueryContext().setScroll(true)).scroll(); + while (rules.hasNext()) { + Rule rule = rules.next(); RuleKey templateKey = rule.templateKey(); + ActiveRule activeRule = activeRules.get(rule.key()); org.sonar.batch.protocol.input.ActiveRule inputActiveRule = new org.sonar.batch.protocol.input.ActiveRule( - activeRule.key().ruleKey().repository(), - activeRule.key().ruleKey().rule(), + rule.key().repository(), + rule.key().rule(), templateKey != null ? templateKey.rule() : null, rule.name(), activeRule.severity(), @@ -243,6 +246,15 @@ public class ProjectRepositoryLoader implements ServerComponent { } } + private Map<RuleKey, ActiveRule> activeRuleByRuleKey(List<ActiveRule> activeRules) { + return Maps.uniqueIndex(activeRules, new Function<ActiveRule, RuleKey>() { + @Override + public RuleKey apply(@Nullable ActiveRule input) { + return input != null ? input.key().ruleKey() : null; + } + }); + } + private void addManualRules(ProjectRepositories ref) { Result<Rule> ruleSearchResult = ruleService.search(new RuleQuery().setRepositories(newArrayList(RuleKey.MANUAL_REPOSITORY_KEY)), new QueryContext().setScroll(true) .setFieldsToReturn(newArrayList(RuleNormalizer.RuleField.KEY.field(), RuleNormalizer.RuleField.NAME.field()))); @@ -309,7 +321,7 @@ public class ProjectRepositoryLoader implements ServerComponent { private Multimap<String, ComponentDto> moduleChildrenByModuleUuid; private TreeModuleSettings(Map<String, String> moduleUuidsByKey, Map<String, Long> moduleIdsByKey, List<ComponentDto> moduleChildren, - List<PropertyDto> moduleChildrenSettings, ComponentDto module) { + List<PropertyDto> moduleChildrenSettings, ComponentDto module) { this.moduleIdsByKey = moduleIdsByKey; this.moduleUuidsByKey = moduleUuidsByKey; propertiesByModuleId = ArrayListMultimap.create(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrations.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrations.java index dd8a452b74c..c2411fa5d4a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrations.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrations.java @@ -90,6 +90,8 @@ public interface DatabaseMigrations { FeedFileSourcesBinaryData.class, FeedSemaphoresLongDates.class, FeedProjectMeasuresLongDates.class, - FeedManualMeasuresLongDates.class - ); + FeedManualMeasuresLongDates.class, + FeedEventsLongDates.class, + AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration.class + ); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Select.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Select.java index d9e4cbffa35..4c5b35143df 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Select.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/Select.java @@ -37,46 +37,74 @@ public interface Select extends SqlStatement<Select> { } @CheckForNull - public Long getLong(int columnIndex) throws SQLException { + public Long getNullableLong(int columnIndex) throws SQLException { long l = rs.getLong(columnIndex); return rs.wasNull() ? null : l; } + public long getLong(int columnIndex) throws SQLException { + return rs.getLong(columnIndex); + } + @CheckForNull - public Double getDouble(int columnIndex) throws SQLException { + public Double getNullableDouble(int columnIndex) throws SQLException { double d = rs.getDouble(columnIndex); return rs.wasNull() ? null : d; } + public double getDouble(int columnIndex) throws SQLException { + return rs.getDouble(columnIndex); + } + @CheckForNull - public Integer getInt(int columnIndex) throws SQLException { + public Integer getNullableInt(int columnIndex) throws SQLException { int i = rs.getInt(columnIndex); return rs.wasNull() ? null : i; } + public int getInt(int columnIndex) throws SQLException { + return rs.getInt(columnIndex); + } + @CheckForNull - public Boolean getBoolean(int columnIndex) throws SQLException { + public Boolean getNullableBoolean(int columnIndex) throws SQLException { boolean b = rs.getBoolean(columnIndex); return rs.wasNull() ? null : b; } + public boolean getBoolean(int columnIndex) throws SQLException { + return rs.getBoolean(columnIndex); + } + @CheckForNull - public String getString(int columnIndex) throws SQLException { + public String getNullableString(int columnIndex) throws SQLException { String s = rs.getString(columnIndex); return rs.wasNull() ? null : s; } + public String getString(int columnIndex) throws SQLException { + return rs.getString(columnIndex); + } + @CheckForNull - public Date getDate(int columnIndex) throws SQLException { + public Date getNullableDate(int columnIndex) throws SQLException { Timestamp t = rs.getTimestamp(columnIndex); return rs.wasNull() ? null : t; } + public Date getDate(int columnIndex) throws SQLException { + return rs.getTimestamp(columnIndex); + } + @CheckForNull - public byte[] getBytes(int columnIndex) throws SQLException { + public byte[] getNullableBytes(int columnIndex) throws SQLException { byte[] b = rs.getBytes(columnIndex); return rs.wasNull() ? null : b; } + + public byte[] getBytes(int columnIndex) throws SQLException { + return rs.getBytes(columnIndex); + } } static interface RowReader<T> { @@ -89,7 +117,7 @@ public interface Select extends SqlStatement<Select> { @Override public Long read(Row row) throws SQLException { - return row.getLong(1); + return row.getNullableLong(1); } } @@ -101,7 +129,7 @@ public interface Select extends SqlStatement<Select> { @Override public String read(Row row) throws SQLException { - return row.getString(1); + return row.getNullableString(1); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v42/CompleteIssueMessageMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v42/CompleteIssueMessageMigration.java index 4cec3c611dc..14259d6b617 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v42/CompleteIssueMessageMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v42/CompleteIssueMessageMigration.java @@ -46,8 +46,8 @@ public class CompleteIssueMessageMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long issueId = row.getLong(1); - String ruleName = row.getString(2); + Long issueId = row.getNullableLong(1); + String ruleName = row.getNullableString(2); update.setString(1, ruleName); update.setLong(2, issueId); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v42/PackageKeysMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v42/PackageKeysMigration.java index 284b1893cdf..1a2e22bc815 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v42/PackageKeysMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v42/PackageKeysMigration.java @@ -50,8 +50,8 @@ public class PackageKeysMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - String key = row.getString(2); + Long id = row.getNullableLong(1); + String key = row.getNullableString(2); update.setString(1, convertKey(key)); update.setLong(2, id); return true; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/ConvertIssueDebtToMinutesMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/ConvertIssueDebtToMinutesMigration.java index 9d867cf4978..091eb2a7e0d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/ConvertIssueDebtToMinutesMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/ConvertIssueDebtToMinutesMigration.java @@ -63,9 +63,9 @@ public class ConvertIssueDebtToMinutesMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long debt = row.getLong(2); + Long debt = row.getNullableLong(2); if (debt != null) { - Long id = row.getLong(1); + Long id = row.getNullableLong(1); update.setLong(1, workDurationConvertor.createFromLong(debt)); update.setDate(2, now); update.setLong(3, id); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/DevelopmentCostMeasuresMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/DevelopmentCostMeasuresMigration.java index 08746ac32c1..6345bc53b03 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/DevelopmentCostMeasuresMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/DevelopmentCostMeasuresMigration.java @@ -28,6 +28,7 @@ import org.sonar.server.db.migrations.Select; import org.sonar.server.db.migrations.SqlStatement; import javax.annotation.CheckForNull; + import java.sql.SQLException; /** @@ -56,8 +57,8 @@ public class DevelopmentCostMeasuresMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - Double value = row.getDouble(2); + Long id = row.getNullableLong(1); + Double value = row.getNullableDouble(2); update.setString(1, convertDebtForDays(value)); update.setLong(2, id); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/IssueChangelogMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/IssueChangelogMigration.java index e0a4397e268..de3c557dc93 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/IssueChangelogMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/IssueChangelogMigration.java @@ -71,8 +71,8 @@ public class IssueChangelogMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - String changeData = row.getString(2); + Long id = row.getNullableLong(1); + String changeData = row.getNullableString(2); update.setString(1, convertChangelog(changeData)); update.setDate(2, now); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/NotResolvedIssuesOnRemovedComponentsMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/NotResolvedIssuesOnRemovedComponentsMigration.java index 6c5c518c893..6c740c6d35b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/NotResolvedIssuesOnRemovedComponentsMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/NotResolvedIssuesOnRemovedComponentsMigration.java @@ -56,7 +56,7 @@ public class NotResolvedIssuesOnRemovedComponentsMigration extends BaseDataChang massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); + Long id = row.getNullableLong(1); update.setString(1, Issue.STATUS_CLOSED); update.setString(2, Issue.RESOLUTION_REMOVED); update.setDate(3, now); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/RequirementMeasuresMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/RequirementMeasuresMigration.java index 02a97f6a8f8..ba68b29e87b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/RequirementMeasuresMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/RequirementMeasuresMigration.java @@ -49,8 +49,8 @@ public class RequirementMeasuresMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - Long ruleId = row.getLong(2); + Long id = row.getNullableLong(1); + Long ruleId = row.getNullableLong(2); update.setLong(1, ruleId); update.setLong(2, id); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigration.java index e5bf642069d..2ce40849d72 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigration.java @@ -30,6 +30,7 @@ import org.sonar.server.db.migrations.SqlStatement; import javax.annotation.CheckForNull; import javax.annotation.Nullable; + import java.sql.SQLException; import java.util.List; @@ -78,13 +79,13 @@ public class TechnicalDebtMeasuresMigration extends BaseDataChange { private class Converter implements MassUpdate.Handler { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - Double value = row.getDouble(2); - Double var1 = row.getDouble(3); - Double var2 = row.getDouble(4); - Double var3 = row.getDouble(5); - Double var4 = row.getDouble(6); - Double var5 = row.getDouble(7); + Long id = row.getNullableLong(1); + Double value = row.getNullableDouble(2); + Double var1 = row.getNullableDouble(3); + Double var2 = row.getNullableDouble(4); + Double var3 = row.getNullableDouble(5); + Double var4 = row.getNullableDouble(6); + Double var5 = row.getNullableDouble(7); update.setLong(1, convertDebtForDays(value)); update.setLong(2, convertDebtForDays(var1)); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java index e6aaf804c74..90c9ed80ffa 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/FeedQProfileKeysMigration.java @@ -55,9 +55,9 @@ public class FeedQProfileKeysMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - String lang = row.getString(2); - String name = row.getString(3); + Long id = row.getNullableLong(1); + String lang = row.getNullableString(2); + String name = row.getNullableString(3); update.setString(1, Slug.slugify(String.format("%s %s %s", lang, name, RandomStringUtils.randomNumeric(5)))); update.setLong(2, id); @@ -74,8 +74,8 @@ public class FeedQProfileKeysMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - String parentKey = row.getString(2); + Long id = row.getNullableLong(1); + String parentKey = row.getNullableString(2); update.setString(1, parentKey); update.setLong(2, id); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/IssueActionPlanKeyMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/IssueActionPlanKeyMigration.java index f15a77b1ff1..9adad2dab5f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/IssueActionPlanKeyMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/IssueActionPlanKeyMigration.java @@ -59,7 +59,7 @@ public class IssueActionPlanKeyMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); + Long id = row.getNullableLong(1); update.setDate(1, now); update.setLong(2, id); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/MeasureDataMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/MeasureDataMigration.java index 8e8a0dc8421..23ecb4c450b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/MeasureDataMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v44/MeasureDataMigration.java @@ -51,8 +51,8 @@ public class MeasureDataMigration extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - Long measureId = row.getLong(2); + Long id = row.getNullableLong(1); + Long measureId = row.getNullableLong(2); update.setLong(1, id); update.setLong(2, measureId); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v45/AddMissingRuleParameterDefaultValuesMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v45/AddMissingRuleParameterDefaultValuesMigration.java index dbe21119526..02b02f8d727 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v45/AddMissingRuleParameterDefaultValuesMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v45/AddMissingRuleParameterDefaultValuesMigration.java @@ -50,7 +50,7 @@ public class AddMissingRuleParameterDefaultValuesMigration extends BaseDataChang .list(new Select.RowReader<RuleParam>() { @Override public RuleParam read(Select.Row row) throws SQLException { - return new RuleParam(row.getLong(1), row.getLong(2), row.getString(3), row.getString(4)); + return new RuleParam(row.getNullableLong(1), row.getNullableLong(2), row.getNullableString(3), row.getNullableString(4)); } }); @@ -63,7 +63,7 @@ public class AddMissingRuleParameterDefaultValuesMigration extends BaseDataChang .list(new Select.RowReader<ActiveRule>() { @Override public ActiveRule read(Select.Row row) throws SQLException { - return new ActiveRule(row.getLong(1), row.getLong(2)); + return new ActiveRule(row.getNullableLong(1), row.getNullableLong(2)); } }); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v451/DeleteUnescapedActivities.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v451/DeleteUnescapedActivities.java index 87e0566b593..49c50bc0980 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v451/DeleteUnescapedActivities.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v451/DeleteUnescapedActivities.java @@ -49,9 +49,9 @@ public class DeleteUnescapedActivities extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - String csv = row.getString(2); + String csv = row.getNullableString(2); if (isUnescaped(csv)) { - update.setLong(1, row.getLong(1)); + update.setLong(1, row.getNullableLong(1)); return true; } return false; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedFileSources.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedFileSources.java index 12bf122af39..fc6aa2d0f2c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedFileSources.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedFileSources.java @@ -150,36 +150,36 @@ public class FeedFileSources extends BaseDataChange { @Override public boolean handle(Row row, SqlStatement update) throws SQLException { - String projectUuid = row.getString(1); - String fileUuid = row.getString(2); - String source = StringUtils.defaultIfBlank(row.getString(3), ""); - Date updatedAt = row.getDate(4); - byte[] shortRevisions = row.getBytes(5); - byte[] longRevisions = row.getBytes(6); - byte[] shortAuthors = row.getBytes(7); - byte[] longAuthors = row.getBytes(8); - byte[] shortDates = row.getBytes(9); - byte[] longDates = row.getBytes(10); - byte[] shortUtHits = row.getBytes(11); - byte[] longUtHits = row.getBytes(12); - byte[] shortUtCond = row.getBytes(13); - byte[] longUtCond = row.getBytes(14); - byte[] shortUtCovCond = row.getBytes(15); - byte[] longUtCovCond = row.getBytes(16); - byte[] shortItHits = row.getBytes(17); - byte[] longItHits = row.getBytes(18); - byte[] shortItCond = row.getBytes(19); - byte[] longItCond = row.getBytes(20); - byte[] shortItCovCond = row.getBytes(21); - byte[] longItCovCond = row.getBytes(22); - byte[] shortOverallHits = row.getBytes(23); - byte[] longOverallHits = row.getBytes(24); - byte[] shortOverallCond = row.getBytes(25); - byte[] longOverallCond = row.getBytes(26); - byte[] shortOverallCovCond = row.getBytes(27); - byte[] longOverallCovCond = row.getBytes(28); - byte[] shortDuplicationData = row.getBytes(29); - byte[] longDuplicationData = row.getBytes(30); + String projectUuid = row.getNullableString(1); + String fileUuid = row.getNullableString(2); + String source = StringUtils.defaultIfBlank(row.getNullableString(3), ""); + Date updatedAt = row.getNullableDate(4); + byte[] shortRevisions = row.getNullableBytes(5); + byte[] longRevisions = row.getNullableBytes(6); + byte[] shortAuthors = row.getNullableBytes(7); + byte[] longAuthors = row.getNullableBytes(8); + byte[] shortDates = row.getNullableBytes(9); + byte[] longDates = row.getNullableBytes(10); + byte[] shortUtHits = row.getNullableBytes(11); + byte[] longUtHits = row.getNullableBytes(12); + byte[] shortUtCond = row.getNullableBytes(13); + byte[] longUtCond = row.getNullableBytes(14); + byte[] shortUtCovCond = row.getNullableBytes(15); + byte[] longUtCovCond = row.getNullableBytes(16); + byte[] shortItHits = row.getNullableBytes(17); + byte[] longItHits = row.getNullableBytes(18); + byte[] shortItCond = row.getNullableBytes(19); + byte[] longItCond = row.getNullableBytes(20); + byte[] shortItCovCond = row.getNullableBytes(21); + byte[] longItCovCond = row.getNullableBytes(22); + byte[] shortOverallHits = row.getNullableBytes(23); + byte[] longOverallHits = row.getNullableBytes(24); + byte[] shortOverallCond = row.getNullableBytes(25); + byte[] longOverallCond = row.getNullableBytes(26); + byte[] shortOverallCovCond = row.getNullableBytes(27); + byte[] longOverallCovCond = row.getNullableBytes(28); + byte[] shortDuplicationData = row.getNullableBytes(29); + byte[] longDuplicationData = row.getNullableBytes(30); String[] sourceData = new FileSourceDto(source, ofNullableBytes(shortRevisions, longRevisions), @@ -235,7 +235,7 @@ public class FeedFileSources extends BaseDataChange { RowReader<Long> simpleLongReader = new RowReader<Long>() { @Override public Long read(Row row) throws SQLException { - Long longValue = row.getLong(1); + Long longValue = row.getNullableLong(1); return longValue == null ? Long.valueOf(0L) : longValue; } }; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedIssueLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedIssueLongDates.java index c97938f721d..9b4dfca0e31 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedIssueLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedIssueLongDates.java @@ -49,9 +49,9 @@ public class FeedIssueLongDates extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - Date createdAt = row.getDate(2); - Date updatedAt = row.getDate(3); + Long id = row.getNullableLong(1); + Date createdAt = row.getNullableDate(2); + Date updatedAt = row.getNullableDate(3); if (createdAt == null) { update.setLong(1, now); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedSnapshotSourcesUpdatedAt.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedSnapshotSourcesUpdatedAt.java index 3c6df32cfd1..589c080501e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedSnapshotSourcesUpdatedAt.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/FeedSnapshotSourcesUpdatedAt.java @@ -52,8 +52,8 @@ public class FeedSnapshotSourcesUpdatedAt extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - update.setDate(1, row.getDate(2)); - update.setLong(2, row.getLong(1)); + update.setDate(1, row.getNullableDate(2)); + update.setLong(2, row.getNullableLong(1)); return true; } }); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/InsertProjectsAuthorizationUpdatedAtMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/InsertProjectsAuthorizationUpdatedAtMigration.java index 026f52a1bda..c4688c28cb6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/InsertProjectsAuthorizationUpdatedAtMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/InsertProjectsAuthorizationUpdatedAtMigration.java @@ -54,7 +54,7 @@ public class InsertProjectsAuthorizationUpdatedAtMigration extends BaseDataChang massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); + Long id = row.getNullableLong(1); update.setLong(1, now); update.setLong(2, id); return true; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/RemoveSortFieldFromIssueFiltersMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/RemoveSortFieldFromIssueFiltersMigration.java index 64d6dc0f4e9..0f8c7f5ab60 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/RemoveSortFieldFromIssueFiltersMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/RemoveSortFieldFromIssueFiltersMigration.java @@ -65,7 +65,7 @@ public class RemoveSortFieldFromIssueFiltersMigration extends BaseDataChange { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - String data = row.getString(2); + String data = row.getNullableString(2); String[] fields = StringUtils.split(data, FIELD_SEPARATOR); boolean found = false; @@ -81,7 +81,7 @@ public class RemoveSortFieldFromIssueFiltersMigration extends BaseDataChange { // data without 'sort' field update.setString(1, StringUtils.join(fieldsToKeep, FIELD_SEPARATOR)); update.setDate(2, now); - update.setLong(3, row.getLong(1)); + update.setLong(3, row.getNullableLong(1)); } return found; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/ReplaceIssueFiltersProjectKeyByUuid.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/ReplaceIssueFiltersProjectKeyByUuid.java index fecf0be4e83..2632f3b27c7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/ReplaceIssueFiltersProjectKeyByUuid.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v50/ReplaceIssueFiltersProjectKeyByUuid.java @@ -64,8 +64,8 @@ public class ReplaceIssueFiltersProjectKeyByUuid extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - String data = row.getString(2); + Long id = row.getNullableLong(1); + String data = row.getNullableString(2); if (data == null) { return false; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration.java new file mode 100644 index 00000000000..60946cef6d4 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration.java @@ -0,0 +1,320 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.db.migrations.v51; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.utils.System2; +import org.sonar.core.persistence.Database; +import org.sonar.server.db.migrations.BaseDataChange; +import org.sonar.server.db.migrations.Select; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +/** + * See http://jira.codehaus.org/browse/SONAR-6187 + * + * Add a new Characteristic 'Usability' with 2 sub-characteristics 'Accessibility' and 'Ease of Use' + * and add a new sub-characteristic 'Compliance' for all characteristics. + * + * Nothing will be done if there's no characteristics in db, as they're all gonna be created by {@link org.sonar.server.startup.RegisterDebtModel} + * + * Before 4.3 the characteristics table contains requirements, then when selecting characteristics we should not forget to exclude them (with a filter on rule_id IS NULL) + * + */ +public class AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration extends BaseDataChange { + + private static final Logger LOGGER = LoggerFactory.getLogger(AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration.class); + + private static final String COMPLIANCE_NAME = "Compliance"; + private static final String COMPLIANCE_KEY_SUFFIX = "_COMPLIANCE"; + + private static final String SECURITY_KEY = "SECURITY"; + private static final String USABILITY_KEY = "USABILITY"; + + private final System2 system; + + public AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration(Database db, System2 system) { + super(db); + this.system = system; + } + + @Override + public void execute(Context context) throws SQLException { + CharacteristicsContext characteristicsContext = new CharacteristicsContext(context, system); + + // On an empty DB, there are no characteristics, they're all gonna be created after in RegisterDebtModel + if (!characteristicsContext.characteristics().isEmpty()) { + int usabilityOder = moveCharacteristicsDownToBeAbleToInsertUsability(characteristicsContext); + createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(characteristicsContext, usabilityOder); + + createSubCharacteristic(characteristicsContext, "REUSABILITY" + COMPLIANCE_KEY_SUFFIX, "Reusability " + COMPLIANCE_NAME, "REUSABILITY"); + createSubCharacteristic(characteristicsContext, "PORTABILITY" + COMPLIANCE_KEY_SUFFIX, "Portability " + COMPLIANCE_NAME, "PORTABILITY"); + createSubCharacteristic(characteristicsContext, "MAINTAINABILITY" + COMPLIANCE_KEY_SUFFIX, "Maintainability " + COMPLIANCE_NAME, "MAINTAINABILITY"); + createSubCharacteristic(characteristicsContext, SECURITY_KEY + COMPLIANCE_KEY_SUFFIX, "Security " + COMPLIANCE_NAME, SECURITY_KEY); + createSubCharacteristic(characteristicsContext, "EFFICIENCY" + COMPLIANCE_KEY_SUFFIX, "Efficiency " + COMPLIANCE_NAME, "EFFICIENCY"); + createSubCharacteristic(characteristicsContext, "CHANGEABILITY" + COMPLIANCE_KEY_SUFFIX, "Changeability " + COMPLIANCE_NAME, "CHANGEABILITY"); + createSubCharacteristic(characteristicsContext, "RELIABILITY" + COMPLIANCE_KEY_SUFFIX, "Reliability " + COMPLIANCE_NAME, "RELIABILITY"); + createSubCharacteristic(characteristicsContext, "TESTABILITY" + COMPLIANCE_KEY_SUFFIX, "Testability " + COMPLIANCE_NAME, "TESTABILITY"); + } + } + + /** + * If the characteristic 'Security' exists, the new characteristic 'Usability' should be inserted just below it, + * so every existing characteristics below Security should move down. + * + * If the characteristic 'Security' does not exists, the new characteristic 'Usability' should be the first one, + * so every existing characteristics should move down. + * + * If the characteristic 'Usability' is already at the right place, nothing will be done. + */ + private int moveCharacteristicsDownToBeAbleToInsertUsability(CharacteristicsContext characteristicsContext) throws SQLException { + Characteristic security = characteristicsContext.findCharacteristicByKey(SECURITY_KEY); + Characteristic usability = characteristicsContext.findCharacteristicByKey(USABILITY_KEY); + + int usabilityOder = 1; + int indexToStart = 0; + if (security != null) { + indexToStart = characteristicsContext.characteristics().indexOf(security) + 1; + usabilityOder = security.getOrder() + 1; + } + + if (usability == null || usability.getOrder() != usabilityOder) { + // Move root characteristics one step lower + for (int i = indexToStart; i < characteristicsContext.characteristics().size(); i++) { + Characteristic characteristic = characteristicsContext.characteristics().get(i); + if (characteristic.getParentId() == null) { + characteristicsContext.updateCharacteristicOrder(characteristic.getKey(), characteristic.getOrder() + 1); + } + } + } + return usabilityOder; + } + + private void createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(CharacteristicsContext characteristicsContext, int newUsabilityOrder) + throws SQLException { + String usabilityKey = USABILITY_KEY; + Characteristic usability = characteristicsContext.findCharacteristicByKey(usabilityKey); + if (usability != null) { + if (usability.getOrder() != newUsabilityOrder) { + usability.setOrder(newUsabilityOrder); + characteristicsContext.updateCharacteristicOrder(usability.getKey(), usability.getOrder()); + } + } else { + usability = new Characteristic().setKey(usabilityKey).setName("Usability").setOrder(newUsabilityOrder); + characteristicsContext.insertCharacteristic(usability); + } + + createSubCharacteristic(characteristicsContext, "USABILITY_ACCESSIBILITY", "Accessibility", usabilityKey); + createSubCharacteristic(characteristicsContext, "USABILITY_EASE_OF_USE", "Ease of Use", usabilityKey); + createSubCharacteristic(characteristicsContext, USABILITY_KEY + COMPLIANCE_KEY_SUFFIX, "Usability " + COMPLIANCE_NAME, usabilityKey); + } + + private void createSubCharacteristic(CharacteristicsContext characteristicsContext, + String subCharacteristicKey, String subCharacteristicName, String parentKey) throws SQLException { + Characteristic parent = characteristicsContext.findCharacteristicByKey(parentKey); + if (parent != null) { + Characteristic subCharacteristic = characteristicsContext.findSubCharacteristicByKey(subCharacteristicKey, parent); + if (subCharacteristic == null) { + characteristicsContext.insertCharacteristic(new Characteristic().setKey(subCharacteristicKey).setName(subCharacteristicName).setParentId(parent.getId())); + } + } + // If the characteristic parent does not exits, the sub-characteristic is not added + } + + private static class Characteristic { + private Integer id; + private String key; + private String name; + private Integer order; + private Integer parentId; + + public Integer getId() { + return id; + } + + public Characteristic setId(Integer id) { + this.id = id; + return this; + } + + public String getKey() { + return key; + } + + public Characteristic setKey(String key) { + this.key = key; + return this; + } + + public String getName() { + return name; + } + + public Characteristic setName(String name) { + this.name = name; + return this; + } + + /** + * On a characteristic, the order can never be null + */ + public Integer getOrder() { + return parentId == null && order != null ? order : null; + } + + public Characteristic setOrder(@Nullable Integer order) { + this.order = order; + return this; + } + + @CheckForNull + public Integer getParentId() { + return parentId; + } + + public Characteristic setParentId(@Nullable Integer parentId) { + this.parentId = parentId; + return this; + } + } + + private static class CharacteristicsContext { + private final System2 system; + Context context; + Date now; + List<Characteristic> characteristics; + + public CharacteristicsContext(Context context, System2 system) throws SQLException { + this.context = context; + this.system = system; + init(); + } + + private void init() throws SQLException { + now = new Date(system.now()); + characteristics = selectEnabledCharacteristics(); + } + + public List<Characteristic> characteristics() { + return characteristics; + } + + @CheckForNull + public Characteristic findCharacteristicByKey(final String key) { + Characteristic characteristic = Iterables.find(characteristics, new Predicate<Characteristic>() { + @Override + public boolean apply(@Nullable Characteristic input) { + return input != null && input.key.equals(key); + } + }, null); + if (characteristic != null && characteristic.getParentId() != null) { + throw new IllegalStateException(String.format("'%s' must be a characteristic", characteristic.getName())); + } + return characteristic; + } + + @CheckForNull + public Characteristic findSubCharacteristicByKey(final String key, Characteristic parent) { + Characteristic characteristic = Iterables.find(characteristics, new Predicate<Characteristic>() { + @Override + public boolean apply(@Nullable Characteristic input) { + return input != null && input.key.equals(key); + } + }, null); + if (characteristic != null) { + Integer parentId = characteristic.getParentId(); + if (parentId == null) { + throw new IllegalStateException(String.format("'%s' must be a sub-characteristic", characteristic.getName())); + } else if (!parentId.equals(parent.getId())) { + throw new IllegalStateException(String.format("'%s' must be defined under '%s'", characteristic.getName(), parent.getName())); + } + } + return characteristic; + } + + private List<Characteristic> selectEnabledCharacteristics() throws SQLException { + return context.prepareSelect( + // Exclude requirements (to not fail when coming from a version older than 4.3) + "SELECT c.id, c.kee, c.name, c.characteristic_order, c.parent_id FROM characteristics c WHERE c.enabled=? AND c.rule_id IS NULL ORDER BY c.characteristic_order") + .setBoolean(1, true) + .list(new CharacteristicReader()); + } + + private int selectCharacteristicId(String key) throws SQLException { + return context.prepareSelect( + "SELECT c.id FROM characteristics c WHERE c.kee = ? AND c.enabled=?") + .setString(1, key) + .setBoolean(2, true) + .get(Select.LONG_READER).intValue(); + } + + public void insertCharacteristic(Characteristic characteristic) throws SQLException { + if (characteristic.getParentId() == null) { + LOGGER.info("Insert new characteristic '{}'", characteristic.getKey()); + } else { + LOGGER.info("Insert new sub characteristic '{}'", characteristic.getKey()); + } + + context.prepareUpsert("INSERT INTO characteristics (kee, name, parent_id, characteristic_order, enabled, created_at) VALUES (?, ?, ?, ?, ?, ?)") + .setString(1, characteristic.getKey()) + .setString(2, characteristic.getName()) + .setInt(3, characteristic.getParentId()) + .setInt(4, characteristic.getOrder()) + .setBoolean(5, true) + .setDate(6, now) + .execute() + .commit(); + characteristic.setId(selectCharacteristicId(characteristic.getKey())); + + characteristics.add(characteristic); + } + + public void updateCharacteristicOrder(String key, Integer order) throws SQLException { + LOGGER.info("Update characteristic '{}' order to {}", key, order); + + context.prepareUpsert("UPDATE characteristics SET characteristic_order=?, updated_at=? WHERE kee=?") + .setInt(1, order) + .setDate(2, now) + .setString(3, key) + .execute() + .commit(); + } + + private static class CharacteristicReader implements Select.RowReader<Characteristic> { + @Override + public Characteristic read(Select.Row row) throws SQLException { + return new Characteristic() + .setId(row.getInt(1)) + .setKey(row.getString(2)) + .setName(row.getString(3)) + .setOrder(row.getNullableInt(4)) + .setParentId(row.getNullableInt(5)); + } + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/CopyScmAccountsFromAuthorsToUsers.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/CopyScmAccountsFromAuthorsToUsers.java index e6ebec997f6..86a1ce33a1f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/CopyScmAccountsFromAuthorsToUsers.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/CopyScmAccountsFromAuthorsToUsers.java @@ -66,7 +66,7 @@ public class CopyScmAccountsFromAuthorsToUsers extends BaseDataChange { ).scroll(new Select.RowHandler() { @Override public void handle(Select.Row row) throws SQLException { - authorsByPersonId.put(row.getLong(1), row.getString(2)); + authorsByPersonId.put(row.getNullableLong(1), row.getNullableString(2)); } }); @@ -126,7 +126,7 @@ public class CopyScmAccountsFromAuthorsToUsers extends BaseDataChange { select.scroll(new Select.RowHandler() { @Override public void handle(Select.Row row) throws SQLException { - users.add(new User(row.getLong(1), row.getString(2), row.getString(3), row.getString(4))); + users.add(new User(row.getNullableLong(1), row.getNullableString(2), row.getNullableString(3), row.getNullableString(4))); } }); return users; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedAnalysisReportsLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedAnalysisReportsLongDates.java index 42c5a6564a1..a0c981df110 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedAnalysisReportsLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedAnalysisReportsLongDates.java @@ -50,11 +50,11 @@ public class FeedAnalysisReportsLongDates extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Date createdAt = row.getDate(1); - Date updatedAt = row.getDate(2); - Date startedAt = row.getDate(3); - Date finishedAt = row.getDate(4); - Long id = row.getLong(5); + Date createdAt = row.getNullableDate(1); + Date updatedAt = row.getNullableDate(2); + Date startedAt = row.getNullableDate(3); + Date finishedAt = row.getNullableDate(4); + Long id = row.getNullableLong(5); update.setLong(1, createdAt == null ? now : Math.min(now, createdAt.getTime())); update.setLong(2, updatedAt == null ? now : Math.min(now, updatedAt.getTime())); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedEventsLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedEventsLongDates.java new file mode 100644 index 00000000000..f7a5ef9332a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedEventsLongDates.java @@ -0,0 +1,76 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.db.migrations.v51; + +import org.sonar.api.utils.System2; +import org.sonar.core.persistence.Database; +import org.sonar.server.db.migrations.BaseDataChange; +import org.sonar.server.db.migrations.MassUpdate; +import org.sonar.server.db.migrations.Select; +import org.sonar.server.db.migrations.SqlStatement; + +import java.sql.SQLException; +import java.util.Date; + +public class FeedEventsLongDates extends BaseDataChange { + + private final System2 system2; + + public FeedEventsLongDates(Database db, System2 system2) { + super(db); + this.system2 = system2; + } + + @Override + public void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate + .select("SELECT e.event_date, e.created_at, e.id FROM events e WHERE event_date_ms IS NULL"); + massUpdate + .update("UPDATE events SET event_date_ms=?, created_at_ms=? WHERE id=?"); + massUpdate.rowPluralName("events"); + massUpdate.execute(new EventDateHandler(system2.now())); + } + + private static class EventDateHandler implements MassUpdate.Handler { + + private final long now; + + public EventDateHandler(long now) { + this.now = now; + } + + @Override + public boolean handle(Select.Row row, SqlStatement update) throws SQLException { + Date eventDate = row.getNullableDate(1); + long eventTime = eventDate == null ? now : Math.min(now, eventDate.getTime()); + update.setLong(1, eventTime); + Date createdAt = row.getNullableDate(2); + update.setLong(2, createdAt == null ? eventTime : Math.min(now, createdAt.getTime())); + + Long id = row.getNullableLong(3); + update.setLong(3, id); + + return true; + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedFileSourcesBinaryData.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedFileSourcesBinaryData.java index ec57d336812..00d1d8afd6c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedFileSourcesBinaryData.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedFileSourcesBinaryData.java @@ -38,6 +38,7 @@ import org.sonar.server.source.db.FileSourceDb; import javax.annotation.Nonnull; import javax.annotation.Nullable; + import java.sql.SQLException; import java.util.Iterator; @@ -55,8 +56,8 @@ public class FeedFileSourcesBinaryData extends BaseDataChange { update.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long fileSourceId = row.getLong(1); - update.setBytes(1, toBinary(fileSourceId, row.getString(2))); + Long fileSourceId = row.getNullableLong(1); + update.setBytes(1, toBinary(fileSourceId, row.getNullableString(2))); update.setLong(2, fileSourceId); return true; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueChangesLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueChangesLongDates.java index 259e82b0060..d522dae4ade 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueChangesLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueChangesLongDates.java @@ -50,10 +50,10 @@ public class FeedIssueChangesLongDates extends BaseDataChange { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Date createdAt = row.getDate(1); - Date updatedAt = row.getDate(2); - Date functionalCreatedAt = row.getDate(3); - Long id = row.getLong(4); + Date createdAt = row.getNullableDate(1); + Date updatedAt = row.getNullableDate(2); + Date functionalCreatedAt = row.getNullableDate(3); + Long id = row.getNullableLong(4); update.setLong(1, createdAt == null ? now : Math.min(now, createdAt.getTime())); update.setLong(2, updatedAt == null ? now : Math.min(now, updatedAt.getTime())); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueComponentUuids.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueComponentUuids.java index 8e07193b67c..7c26de3a13a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueComponentUuids.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueComponentUuids.java @@ -47,9 +47,9 @@ public class FeedIssueComponentUuids extends BaseDataChange { update.execute(new Handler() { @Override public boolean handle(Row row, SqlStatement update) throws SQLException { - update.setString(1, row.getString(1)); - update.setString(2, row.getString(2)); - update.setLong(3, row.getLong(3)); + update.setString(1, row.getNullableString(1)); + update.setString(2, row.getNullableString(2)); + update.setLong(3, row.getNullableLong(3)); return true; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueTags.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueTags.java index cd38aa0741e..5eaf90f8f48 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueTags.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssueTags.java @@ -56,10 +56,10 @@ public class FeedIssueTags extends BaseDataChange { context.prepareSelect("SELECT id, system_tags, tags FROM rules").scroll(new RowHandler() { @Override public void handle(Row row) throws SQLException { - Integer id = row.getInt(1); + Integer id = row.getNullableInt(1); tagsByRuleId.put(id, StringUtils.trimToNull(TAG_JOINER.join( - StringUtils.trimToNull(row.getString(2)), - StringUtils.trimToNull(row.getString(3))))); + StringUtils.trimToNull(row.getNullableString(2)), + StringUtils.trimToNull(row.getNullableString(3))))); } }); @@ -69,8 +69,8 @@ public class FeedIssueTags extends BaseDataChange { update.execute(new Handler() { @Override public boolean handle(Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - Integer ruleId = row.getInt(2); + Long id = row.getNullableLong(1); + Integer ruleId = row.getNullableInt(2); boolean updated = false; if (tagsByRuleId.get(ruleId) != null) { updated = true; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssuesLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssuesLongDates.java index 5734005ed6f..81a690a38e9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssuesLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedIssuesLongDates.java @@ -51,10 +51,10 @@ public class FeedIssuesLongDates extends BaseDataChange { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { for (int i = 1; i <= 3; i++) { - update.setLong(i, row.getDate(i) == null ? null : Math.min(now, row.getDate(i).getTime())); + update.setLong(i, row.getNullableDate(i) == null ? null : Math.min(now, row.getNullableDate(i).getTime())); } - Long id = row.getLong(4); + Long id = row.getNullableLong(4); update.setLong(4, id); return true; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedManualMeasuresLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedManualMeasuresLongDates.java index 78529fcfa6c..d127b7f8f71 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedManualMeasuresLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedManualMeasuresLongDates.java @@ -47,16 +47,16 @@ public class FeedManualMeasuresLongDates extends BaseDataChange { .select("SELECT m.created_at, m.updated_at, m.id FROM manual_measures m WHERE created_at_ms IS NULL"); massUpdate .update("UPDATE manual_measures SET created_at_ms=?, updated_at_ms=? WHERE id=?"); - massUpdate.rowPluralName("manualMeasures"); + massUpdate.rowPluralName("manual measures"); massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { for (int i = 1; i <= 2; i++) { - Date date = row.getDate(i); + Date date = row.getNullableDate(i); update.setLong(i, date == null ? null : Math.min(now, date.getTime())); } - Long id = row.getLong(3); + Long id = row.getNullableLong(3); update.setLong(3, id); return true; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedProjectMeasuresLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedProjectMeasuresLongDates.java index 6cea58309dc..dce97a78d7d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedProjectMeasuresLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedProjectMeasuresLongDates.java @@ -47,14 +47,14 @@ public class FeedProjectMeasuresLongDates extends BaseDataChange { .select("SELECT m.measure_date, m.id FROM project_measures m WHERE measure_date_ms IS NULL"); massUpdate .update("UPDATE project_measures SET measure_date_ms=? WHERE id=?"); - massUpdate.rowPluralName("projectMeasures"); + massUpdate.rowPluralName("project measures"); massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Date date = row.getDate(1); + Date date = row.getNullableDate(1); update.setLong(1, date == null ? null : Math.min(now, date.getTime())); - Long id = row.getLong(2); + Long id = row.getNullableLong(2); update.setLong(2, id); return true; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedSemaphoresLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedSemaphoresLongDates.java index 1ddc3a03c71..bf4ddd08419 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedSemaphoresLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedSemaphoresLongDates.java @@ -52,11 +52,11 @@ public class FeedSemaphoresLongDates extends BaseDataChange { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { for (int i = 1; i <= 3; i++) { - Date date = row.getDate(i); + Date date = row.getNullableDate(i); update.setLong(i, date == null ? null : Math.min(now, date.getTime())); } - Long id = row.getLong(4); + Long id = row.getNullableLong(4); update.setLong(4, id); return true; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedSnapshotsLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedSnapshotsLongDates.java index 09998ea93eb..2897eb7dd1d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedSnapshotsLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedSnapshotsLongDates.java @@ -51,10 +51,10 @@ public class FeedSnapshotsLongDates extends BaseDataChange { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { for (int i = 1; i <= 7; i++) { - update.setLong(i, row.getDate(i) == null ? null : Math.min(now, row.getDate(i).getTime())); + update.setLong(i, row.getNullableDate(i) == null ? null : Math.min(now, row.getNullableDate(i).getTime())); } - Long id = row.getLong(8); + Long id = row.getNullableLong(8); update.setLong(8, id); return true; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedUsersLongDates.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedUsersLongDates.java index 5324120c6e3..3907ddf94b1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedUsersLongDates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/FeedUsersLongDates.java @@ -57,9 +57,9 @@ public class FeedUsersLongDates extends BaseDataChange { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - Date createdAt = row.getDate(2); - Date updatedAt = row.getDate(3); + Long id = row.getNullableLong(1); + Date createdAt = row.getNullableDate(2); + Date updatedAt = row.getNullableDate(3); if (createdAt == null) { update.setLong(1, now); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/RenameComponentRelatedParamsInIssueFilters.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/RenameComponentRelatedParamsInIssueFilters.java index b74b5ac3d97..e7862b474c6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/RenameComponentRelatedParamsInIssueFilters.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/RenameComponentRelatedParamsInIssueFilters.java @@ -68,7 +68,7 @@ public class RenameComponentRelatedParamsInIssueFilters extends BaseDataChange { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - String data = row.getString(2); + String data = row.getNullableString(2); String[] fields = StringUtils.split(data, FIELD_SEPARATOR); List<String> fieldsToKeep = Lists.newArrayList(); @@ -83,7 +83,7 @@ public class RenameComponentRelatedParamsInIssueFilters extends BaseDataChange { } update.setString(1, StringUtils.join(fieldsToKeep, FIELD_SEPARATOR)); update.setDate(2, now); - update.setLong(3, row.getLong(1)); + update.setLong(3, row.getNullableLong(1)); return true; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/UpdateProjectsModuleUuidPath.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/UpdateProjectsModuleUuidPath.java index 28fe41810ff..751926fb06b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/UpdateProjectsModuleUuidPath.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/v51/UpdateProjectsModuleUuidPath.java @@ -22,9 +22,11 @@ package org.sonar.server.db.migrations.v51; import org.apache.commons.lang.StringUtils; import org.sonar.core.persistence.Database; -import org.sonar.server.db.migrations.*; +import org.sonar.server.db.migrations.BaseDataChange; +import org.sonar.server.db.migrations.MassUpdate; import org.sonar.server.db.migrations.MassUpdate.Handler; import org.sonar.server.db.migrations.Select.Row; +import org.sonar.server.db.migrations.SqlStatement; import javax.annotation.Nullable; @@ -53,11 +55,11 @@ public class UpdateProjectsModuleUuidPath extends BaseDataChange { private static final class ModuleUuidPathUpdateHandler implements Handler { @Override public boolean handle(Row row, SqlStatement update) throws SQLException { - Long id = row.getLong(1); - String moduleUuidPath = row.getString(2); - String uuid = row.getString(3); - String scope = row.getString(4); - String qualifier = row.getString(5); + Long id = row.getNullableLong(1); + String moduleUuidPath = row.getNullableString(2); + String uuid = row.getNullableString(3); + String scope = row.getNullableString(4); + String qualifier = row.getNullableString(5); boolean needUpdate = false; String newModuleUuidPath = moduleUuidPath; diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/request/ProxyBulkRequestBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/es/request/ProxyBulkRequestBuilder.java index 480e2b74915..66aee7b890e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/request/ProxyBulkRequestBuilder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/request/ProxyBulkRequestBuilder.java @@ -20,6 +20,10 @@ package org.sonar.server.es.request; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multiset.Entry; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ListenableActionFuture; import org.elasticsearch.action.bulk.BulkRequestBuilder; @@ -32,6 +36,8 @@ import org.elasticsearch.common.unit.TimeValue; import org.sonar.core.profiling.Profiling; import org.sonar.core.profiling.StopWatch; +import java.util.Set; + public class ProxyBulkRequestBuilder extends BulkRequestBuilder { private final Profiling profiling; @@ -79,23 +85,71 @@ public class ProxyBulkRequestBuilder extends BulkRequestBuilder { public String toString() { StringBuilder message = new StringBuilder(); message.append("Bulk["); + HashMultiset<BulkRequestKey> groupedRequests = HashMultiset.create(); for (int i=0 ; i<request.requests().size() ; i++) { ActionRequest item = request.requests().get(i); + String requestType, index, docType; if (item instanceof IndexRequest) { IndexRequest request = (IndexRequest) item; - message.append(String.format("index id '%s' in %s/%s", request.id(), request.index(), request.type())); + requestType = "index"; + index = request.index(); + docType = request.type(); } else if (item instanceof UpdateRequest) { UpdateRequest request = (UpdateRequest) item; - message.append(String.format("update id '%s' in %s/%s", request.id(), request.index(), request.type())); + requestType = "update"; + index = request.index(); + docType = request.type(); } else if (item instanceof DeleteRequest) { DeleteRequest request = (DeleteRequest) item; - message.append(String.format("delete id '%s' from %s/%s", request.id(), request.index(), request.type())); + requestType = "delete"; + index = request.index(); + docType = request.type(); + } else { + // Cannot happen, not allowed by BulkRequest's contract + throw new IllegalStateException("Unsupported bulk request type: " + item.getClass()); } - if (i < request.requests().size()-1) { + groupedRequests.add(new BulkRequestKey(requestType, index, docType)); + } + + Set<Entry<BulkRequestKey>> entrySet = groupedRequests.entrySet(); + int size = entrySet.size(); + int current = 0; + for (Entry<BulkRequestKey> requestEntry : entrySet) { + message.append(requestEntry.getCount()).append(" ").append(requestEntry.getElement().toString()); + current++; + if (current < size) { message.append(", "); } } + message.append("]"); return message.toString(); } + + private static class BulkRequestKey { + private String requestType; + private String index; + private String docType; + + private BulkRequestKey(String requestType, String index, String docType) { + this.requestType = requestType; + this.index = index; + this.docType = docType; + } + + @Override + public boolean equals(Object obj) { + return EqualsBuilder.reflectionEquals(this, obj); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + @Override + public String toString() { + return String.format("%s request(s) on index %s and type %s", requestType, index, docType); + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/request/ProxyDeleteByQueryRequestBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/es/request/ProxyDeleteByQueryRequestBuilder.java index f473fc305b2..0d1474e66ff 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/request/ProxyDeleteByQueryRequestBuilder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/request/ProxyDeleteByQueryRequestBuilder.java @@ -26,13 +26,21 @@ import org.elasticsearch.action.deletebyquery.DeleteByQueryRequestBuilder; import org.elasticsearch.action.deletebyquery.DeleteByQueryResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.index.query.QueryBuilder; import org.sonar.core.profiling.Profiling; import org.sonar.core.profiling.StopWatch; +import java.io.IOException; + public class ProxyDeleteByQueryRequestBuilder extends DeleteByQueryRequestBuilder { private final Profiling profiling; + private QueryBuilder internalBuilder; + public ProxyDeleteByQueryRequestBuilder(Client client, Profiling profiling) { super(client); this.profiling = profiling; @@ -68,12 +76,31 @@ public class ProxyDeleteByQueryRequestBuilder extends DeleteByQueryRequestBuilde } @Override + public DeleteByQueryRequestBuilder setQuery(QueryBuilder queryBuilder) { + this.internalBuilder = queryBuilder; + return super.setQuery(queryBuilder); + } + + @Override public String toString() { StringBuilder message = new StringBuilder(); - message.append("ES delete by query request"); + message.append(String.format("ES delete by query request '%s'", xContentToString(internalBuilder))); if (request.indices().length > 0) { message.append(String.format(" on indices '%s'", StringUtils.join(request.indices(), ","))); } return message.toString(); } + + private String xContentToString(ToXContent toXContent) { + if (internalBuilder == null) { + return ""; + } + try { + XContentBuilder builder = XContentFactory.jsonBuilder(); + toXContent.toXContent(builder, ToXContent.EMPTY_PARAMS); + return builder.string(); + } catch (IOException e) { + throw new IllegalStateException("Fail to convert request to string", e); + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/RuleCreator.java b/server/sonar-server/src/main/java/org/sonar/server/rule/RuleCreator.java index aa17fafdffc..aeda87979ea 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/RuleCreator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/RuleCreator.java @@ -20,27 +20,37 @@ package org.sonar.server.rule; +import com.google.common.base.Splitter; import com.google.common.base.Strings; import org.sonar.api.ServerComponent; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; +import org.sonar.api.server.rule.RuleParamType; import org.sonar.core.persistence.DbSession; import org.sonar.core.rule.RuleDto; import org.sonar.core.rule.RuleDto.Format; import org.sonar.core.rule.RuleParamDto; import org.sonar.server.db.DbClient; import org.sonar.server.rule.index.RuleDoc; +import org.sonar.server.util.TypeValidations; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; + public class RuleCreator implements ServerComponent { private final DbClient dbClient; - public RuleCreator(DbClient dbClient) { + private final TypeValidations typeValidations; + + public RuleCreator(DbClient dbClient, TypeValidations typeValidations) { this.dbClient = dbClient; + this.typeValidations = typeValidations; } public RuleKey create(NewRule newRule) { @@ -71,6 +81,7 @@ public class RuleCreator implements ServerComponent { throw new IllegalArgumentException("This rule is not a template rule: " + templateKey.toString()); } validateCustomRule(newRule); + validateCustomRuleParams(newRule, dbSession, templateKey); RuleKey customRuleKey = RuleKey.of(templateRule.getRepositoryKey(), newRule.ruleKey()); @@ -116,6 +127,25 @@ public class RuleCreator implements ServerComponent { } } + protected void validateCustomRuleParams(NewRule newRule, DbSession dbSession, RuleKey templateKey) { + for (RuleParamDto ruleParam : dbClient.ruleDao().findRuleParamsByRuleKey(dbSession, templateKey)) { + validateParam(ruleParam, newRule.parameter(ruleParam.getName())); + } + } + + @CheckForNull + private void validateParam(RuleParamDto ruleParam, @Nullable String value) { + if (value != null) { + RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType()); + if (ruleParamType.multiple()) { + List<String> values = newArrayList(Splitter.on(",").split(value)); + typeValidations.validate(values, ruleParamType.type(), ruleParamType.values()); + } else { + typeValidations.validate(value, ruleParamType.type(), ruleParamType.values()); + } + } + } + private static void validateManualRule(NewRule newRule) { validateRuleKey(newRule.ruleKey()); validateName(newRule); diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java index 06d92231bd4..68fced94686 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java @@ -221,12 +221,8 @@ public class UserSession { * Ensures that user implies the specified project permission on a component. If not a {@link org.sonar.server.exceptions.ForbiddenException} is thrown. */ public UserSession checkComponentPermission(String projectPermission, String componentKey) { - return checkComponentPermission(projectPermission, componentKey, INSUFFICIENT_PRIVILEGES_MESSAGE); - } - - public UserSession checkComponentPermission(String projectPermission, String componentKey, @Nullable String errorMessage) { if (!hasComponentPermission(projectPermission, componentKey)) { - throw new ForbiddenException(errorMessage); + throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); } return this; } diff --git a/server/sonar-server/src/main/resources/com/sonar/sqale/technical-debt-model.xml b/server/sonar-server/src/main/resources/com/sonar/sqale/technical-debt-model.xml index d2276e82e5d..4e3277a44b6 100644 --- a/server/sonar-server/src/main/resources/com/sonar/sqale/technical-debt-model.xml +++ b/server/sonar-server/src/main/resources/com/sonar/sqale/technical-debt-model.xml @@ -1,24 +1,3 @@ -<!-- - - SonarQube, open source software quality management tool. - Copyright (C) 2008-2014 SonarSource - mailto:contact AT sonarsource DOT com - - SonarQube 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. - - SonarQube 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 this program; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - ---> <sqale> <chc> <key>REUSABILITY</key> @@ -28,6 +7,10 @@ <name>Modularity</name> </chc> <chc> + <key>REUSABILITY_COMPLIANCE</key> + <name>Reusability Compliance</name> + </chc> + <chc> <key>TRANSPORTABILITY</key> <name>Transportability</name> </chc> @@ -52,6 +35,10 @@ <name>OS</name> </chc> <chc> + <key>PORTABILITY_COMPLIANCE</key> + <name>Portability Compliance</name> + </chc> + <chc> <key>SOFTWARE_RELATED_PORTABILITY</key> <name>Software</name> </chc> @@ -64,6 +51,10 @@ <key>MAINTAINABILITY</key> <name>Maintainability</name> <chc> + <key>MAINTAINABILITY_COMPLIANCE</key> + <name>Maintainability Compliance</name> + </chc> + <chc> <key>READABILITY</key> <name>Readability</name> </chc> @@ -88,11 +79,31 @@ <name>Input validation and representation</name> </chc> <chc> + <key>SECURITY_COMPLIANCE</key> + <name>Security Compliance</name> + </chc> + <chc> <key>SECURITY_FEATURES</key> <name>Security features</name> </chc> </chc> <chc> + <key>USABILITY</key> + <name>Usability</name> + <chc> + <key>USABILITY_ACCESSIBILITY</key> + <name>Accessibility</name> + </chc> + <chc> + <key>USABILITY_EASE_OF_USE</key> + <name>Ease of Use</name> + </chc> + <chc> + <key>USABILITY_COMPLIANCE</key> + <name>Usability Compliance</name> + </chc> + </chc> + <chc> <key>EFFICIENCY</key> <name>Efficiency</name> <chc> @@ -100,6 +111,10 @@ <name>Processor use</name> </chc> <chc> + <key>EFFICIENCY_COMPLIANCE</key> + <name>Efficiency Compliance</name> + </chc> + <chc> <key>MEMORY_EFFICIENCY</key> <name>Memory use</name> </chc> @@ -116,6 +131,10 @@ <name>Architecture</name> </chc> <chc> + <key>CHANGEABILITY_COMPLIANCE</key> + <name>Changeability Compliance</name> + </chc> + <chc> <key>DATA_CHANGEABILITY</key> <name>Data</name> </chc> @@ -152,6 +171,10 @@ <name>Logic</name> </chc> <chc> + <key>RELIABILITY_COMPLIANCE</key> + <name>Reliability Compliance</name> + </chc> + <chc> <key>RESOURCE_RELIABILITY</key> <name>Resource</name> </chc> @@ -172,6 +195,10 @@ <name>Integration level</name> </chc> <chc> + <key>TESTABILITY_COMPLIANCE</key> + <name>Testability Compliance</name> + </chc> + <chc> <key>UNIT_TESTABILITY</key> <name>Unit level</name> </chc> diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectRepositoryLoaderMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectRepositoryLoaderMediumTest.java index 1a1f7a62003..ea73b0dea18 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectRepositoryLoaderMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectRepositoryLoaderMediumTest.java @@ -86,7 +86,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void return_project_settings() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -110,7 +110,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void not_returned_secured_settings_with_only_preview_permission() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PREVIEW_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PREVIEW_EXECUTION).addProjectUuidPermissions(UserRole.USER, project.uuid()); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -133,7 +133,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void return_project_with_module_settings() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -169,7 +169,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void return_project_with_module_settings_inherited_from_project() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -200,7 +200,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void return_project_with_module_with_sub_module() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -248,7 +248,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void return_project_with_two_modules() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -293,7 +293,7 @@ public class ProjectRepositoryLoaderMediumTest { public void return_provisioned_project_settings() throws Exception { // No snapshot attached on the project -> provisioned project ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -323,7 +323,7 @@ public class ProjectRepositoryLoaderMediumTest { // No module properties ComponentDto subModule = ComponentTesting.newModuleDto(module); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), subModule.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, subModule); // Sub module properties @@ -359,7 +359,7 @@ public class ProjectRepositoryLoaderMediumTest { tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(module.getId()), dbSession); ComponentDto subModule = ComponentTesting.newModuleDto(module); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), subModule.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, subModule); // Sub module properties @@ -393,7 +393,7 @@ public class ProjectRepositoryLoaderMediumTest { // No module property ComponentDto subModule = ComponentTesting.newModuleDto(module); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), subModule.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, subModule); // No sub module property @@ -426,7 +426,7 @@ public class ProjectRepositoryLoaderMediumTest { tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER").setResourceId(module.getId()), dbSession); ComponentDto subModule = ComponentTesting.newModuleDto(module); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), subModule.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, subModule); // No sub module property @@ -447,7 +447,7 @@ public class ProjectRepositoryLoaderMediumTest { Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100"); ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt( @@ -471,7 +471,7 @@ public class ProjectRepositoryLoaderMediumTest { Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100"); ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt( @@ -495,7 +495,7 @@ public class ProjectRepositoryLoaderMediumTest { Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100"); ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt( @@ -541,7 +541,7 @@ public class ProjectRepositoryLoaderMediumTest { // No snapshot attached on the project -> provisioned project ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt( @@ -563,7 +563,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void fail_when_no_quality_profile_for_a_language() throws Exception { ComponentDto project = ComponentTesting.newProjectDto().setKey("org.codehaus.sonar:sonar"); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); dbSession.commit(); @@ -580,7 +580,7 @@ public class ProjectRepositoryLoaderMediumTest { Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100"); ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt( @@ -614,11 +614,34 @@ public class ProjectRepositoryLoaderMediumTest { } @Test + public void return_more_than_10_active_rules() throws Exception { + ComponentDto project = ComponentTesting.newProjectDto(); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); + tester.get(DbClient.class).componentDao().insert(dbSession, project); + + QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd") + .setRulesUpdatedAt(DateUtils.formatDateTime(DateUtils.parseDateTime("2014-01-14T13:00:00+0100"))); + tester.get(DbClient.class).qualityProfileDao().insert(dbSession, profileDto); + tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.profile.xoo").setValue("SonarQube way"), dbSession); + + for (int i = 0; i<20; i++) { + RuleKey ruleKey = RuleKey.of("squid", "Rule" + i); + tester.get(DbClient.class).ruleDao().insert(dbSession, RuleTesting.newDto(ruleKey).setName("Rule" + i).setLanguage(ServerTester.Xoo.KEY)); + tester.get(RuleActivator.class).activate(dbSession, new RuleActivation(ruleKey).setSeverity(Severity.MINOR), profileDto.getKey()); + } + + dbSession.commit(); + + ProjectRepositories ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key())); + assertThat(ref.activeRules()).hasSize(20); + } + + @Test public void return_custom_rule() throws Exception { Date ruleUpdatedAt = DateUtils.parseDateTime("2014-01-14T13:00:00+0100"); ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentUuidPermission(UserRole.USER, project.uuid(), project.uuid()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); QualityProfileDto profileDto = QProfileTesting.newDto(QProfileName.createFor(ServerTester.Xoo.KEY, "SonarQube way"), "abcd").setRulesUpdatedAt( @@ -649,7 +672,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void return_manual_rules() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -704,7 +727,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void return_file_data_from_single_project() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -723,7 +746,7 @@ public class ProjectRepositoryLoaderMediumTest { @Test public void return_file_data_from_multi_modules() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), project.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, project); addDefaultProfile(); @@ -759,7 +782,7 @@ public class ProjectRepositoryLoaderMediumTest { tester.get(FileSourceDao.class).insert(newFileSourceDto(projectFile).setSrcHash("123456")); ComponentDto module = ComponentTesting.newModuleDto(project); - MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION).addComponentPermission(UserRole.USER, project.getKey(), module.getKey()); + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); tester.get(DbClient.class).componentDao().insert(dbSession, module); // File on module diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/BaseDataChangeTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/BaseDataChangeTest.java index 09a5c0a11c4..cb156004ef6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/BaseDataChangeTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/BaseDataChangeTest.java @@ -134,7 +134,7 @@ public class BaseDataChangeTest extends AbstractDaoTestCase { context.prepareSelect("select id from persons order by id desc").scroll(new Select.RowHandler() { @Override public void handle(Select.Row row) throws SQLException { - ids.add(row.getLong(1)); + ids.add(row.getNullableLong(1)); } }); } @@ -252,7 +252,7 @@ public class BaseDataChangeTest extends AbstractDaoTestCase { context.prepareSelect("select id from persons").scroll(new Select.RowHandler() { @Override public void handle(Select.Row row) throws SQLException { - long id = row.getLong(1); + long id = row.getNullableLong(1); upsert.setString(1, "login" + id).setInt(2, 10 + (int) id).setLong(3, id); upsert.execute(); } @@ -277,7 +277,7 @@ public class BaseDataChangeTest extends AbstractDaoTestCase { massUpdate.execute(new MassUpdate.Handler() { @Override public boolean handle(Select.Row row, SqlStatement update) throws SQLException { - long id = row.getLong(1); + long id = row.getNullableLong(1); update .setString(1, "login" + id) .setInt(2, 10 + (int) id) @@ -339,17 +339,52 @@ public class BaseDataChangeTest extends AbstractDaoTestCase { } } + @Test + public void read_not_null_fields() throws Exception { + db.prepareDbUnit(getClass(), "persons.xml"); + + final List<Object[]> persons = new ArrayList<Object[]>(); + new BaseDataChange(db.database()) { + @Override + public void execute(Context context) throws SQLException { + persons.addAll(context + .prepareSelect("select id,login,age,enabled,updated_at,coeff from persons where id=2") + .list(new Select.RowReader<Object[]>() { + @Override + public Object[] read(Select.Row row) throws SQLException { + return new Object[] { + // id, login, age, enabled + row.getLong(1), + row.getString(2), + row.getInt(3), + row.getBoolean(4), + row.getDate(5), + row.getDouble(6), + }; + } + })); + } + }.execute(); + assertThat(persons).hasSize(1); + assertThat(persons.get(0)[0]).isEqualTo(2L); + assertThat(persons.get(0)[1]).isEqualTo("emmerik"); + assertThat(persons.get(0)[2]).isEqualTo(14); + assertThat(persons.get(0)[3]).isEqualTo(true); + assertThat(persons.get(0)[4]).isNotNull(); + assertThat(persons.get(0)[5]).isEqualTo(5.2); + } + static class UserReader implements Select.RowReader<Object[]> { @Override public Object[] read(Select.Row row) throws SQLException { - return new Object[]{ + return new Object[] { // id, login, age, enabled - row.getLong(1), - row.getString(2), - row.getInt(3), - row.getBoolean(4), - row.getDate(5), - row.getDouble(6), + row.getNullableLong(1), + row.getNullableString(2), + row.getNullableInt(3), + row.getNullableBoolean(4), + row.getNullableDate(5), + row.getNullableDouble(6), }; } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest.java new file mode 100644 index 00000000000..f14fbebf3c2 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest.java @@ -0,0 +1,131 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.db.migrations.v51; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.System2; +import org.sonar.core.persistence.DbTester; +import org.sonar.server.db.migrations.DatabaseMigration; + +import static junit.framework.TestCase.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest { + + @ClassRule + public static DbTester db = new DbTester().schema(AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest.class, "schema.sql"); + + DatabaseMigration migration; + + System2 system = mock(System2.class); + + @Before + public void setUp() throws Exception { + db.executeUpdateSql("truncate table characteristics"); + + when(system.now()).thenReturn(DateUtils.parseDate("2015-02-15").getTime()); + + migration = new AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration(db.database(), system); + } + + @Test + public void migrate() throws Exception { + db.prepareDbUnit(getClass(), "migrate.xml"); + migration.execute(); + db.assertDbUnit(getClass(), "migrate-result.xml", "characteristics"); + } + + @Test + public void do_nothing_when_already_migrated() throws Exception { + db.prepareDbUnit(getClass(), "do_nothing_when_already_migrated.xml"); + migration.execute(); + db.assertDbUnit(getClass(), "do_nothing_when_already_migrated.xml", "characteristics"); + } + + @Test + public void do_nothing_when_no_characteristics() throws Exception { + db.prepareDbUnit(getClass(), "empty.xml"); + migration.execute(); + assertThat(db.countRowsOfTable("characteristics")).isEqualTo(0); + } + + @Test + public void insert_usability_at_the_top_if_security_does_exists() throws Exception { + db.prepareDbUnit(getClass(), "insert_usability_at_the_top_if_security_does_exists.xml"); + migration.execute(); + db.assertDbUnit(getClass(), "insert_usability_at_the_top_if_security_does_exists-result.xml", "characteristics"); + } + + @Test + public void update_usability_order_if_already_exists() throws Exception { + db.prepareDbUnit(getClass(), "update_usability_if_already_exists.xml"); + migration.execute(); + db.assertDbUnit(getClass(), "update_usability_if_already_exists-result.xml", "characteristics"); + } + + @Test + public void fail_if_usability_exists_as_sub_characteristic() throws Exception { + db.prepareDbUnit(getClass(), "fail_if_usability_exists_as_sub_characteristic.xml"); + + try { + migration.execute(); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("'Usability' must be a characteristic"); + } + } + + @Test + public void fail_if_compliance_already_exists_as_characteristic() throws Exception { + db.prepareDbUnit(getClass(), "fail_if_compliance_already_exists_as_characteristic.xml"); + + try { + migration.execute(); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("'Compliance' must be a sub-characteristic"); + } + } + + @Test + public void fail_if_compliance_already_exists_under_wrong_characteristic() throws Exception { + db.prepareDbUnit(getClass(), "fail_if_compliance_already_exists_under_wrong_characteristic.xml"); + + try { + migration.execute(); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("'Reusability Compliance' must be defined under 'Reusability'"); + } + } + + @Test + public void not_fail_if_some_deprecated_requirements_still_exists_in_db() throws Exception { + db.prepareDbUnit(getClass(), "not_fail_if_some_deprecated_requirements_still_exists_in_db.xml"); + migration.execute(); + db.assertDbUnit(getClass(), "not_fail_if_some_deprecated_requirements_still_exists_in_db.xml", "characteristics"); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest.java new file mode 100644 index 00000000000..4076ba2e382 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest.java @@ -0,0 +1,88 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.db.migrations.v51; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.core.persistence.DbTester; +import org.sonar.server.db.migrations.DatabaseMigration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.utils.DateUtils.parseDate; + +public class FeedEventsLongDatesTest { + @ClassRule + public static DbTester db = new DbTester().schema(FeedEventsLongDatesTest.class, "schema.sql"); + + @Before + public void before() throws Exception { + db.prepareDbUnit(getClass(), "before.xml"); + } + + @Test + public void execute() throws Exception { + DatabaseMigration migration = newMigration(System2.INSTANCE); + + migration.execute(); + + int count = db + .countSql("select count(*) from events where " + + "created_at_ms is not null " + + "and event_date_ms is not null"); + assertThat(count).isEqualTo(3); + } + + @Test + public void take_now_if_date_in_the_future() throws Exception { + System2 system = mock(System2.class); + when(system.now()).thenReturn(1234L); + + DatabaseMigration migration = newMigration(system); + + migration.execute(); + + int count = db + .countSql("select count(*) from events where " + + "created_at_ms = 1234"); + assertThat(count).isEqualTo(2); + } + + @Test + public void take_date_if_in_the_past() throws Exception { + DatabaseMigration migration = newMigration(System2.INSTANCE); + + migration.execute(); + + long time = parseDate("2014-09-25").getTime(); + int count = db + .countSql("select count(*) from events where " + + "created_at_ms=" + time); + assertThat(count).isEqualTo(1); + } + + private DatabaseMigration newMigration(System2 system) { + return new FeedEventsLongDates(db.database(), system); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/FeedManualMeasuresLongDatesTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/FeedManualMeasuresLongDatesTest.java index c3b2a47c45d..d91efe79024 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/FeedManualMeasuresLongDatesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/v51/FeedManualMeasuresLongDatesTest.java @@ -70,7 +70,7 @@ public class FeedManualMeasuresLongDatesTest { } @Test - public void take_snapshot_date_if_in_the_past() throws Exception { + public void take_manual_measure_date_if_in_the_past() throws Exception { DatabaseMigration migration = newMigration(System2.INSTANCE); migration.execute(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/debt/DebtMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/debt/DebtMediumTest.java index c0b0c174862..d01b7299644 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/debt/DebtMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/debt/DebtMediumTest.java @@ -44,14 +44,14 @@ public class DebtMediumTest { } @Test - public void find_characteristics() throws Exception { + public void find_default_characteristics() throws Exception { DebtModelService debtModelService = serverTester.get(DebtModelService.class); // Only root characteristics - assertThat(debtModelService.characteristics()).hasSize(8); + assertThat(debtModelService.characteristics()).hasSize(9); // Characteristics and sub-characteristics - assertThat(debtModelService.allCharacteristics()).hasSize(39); + assertThat(debtModelService.allCharacteristics()).hasSize(51); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/request/ProxyBulkRequestBuilderTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/request/ProxyBulkRequestBuilderTest.java index f96e451dd0b..e79cc839d28 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/es/request/ProxyBulkRequestBuilderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/es/request/ProxyBulkRequestBuilderTest.java @@ -66,6 +66,9 @@ public class ProxyBulkRequestBuilderTest { req.add(new IndexRequest(FakeIndexDefinition.INDEX, FakeIndexDefinition.TYPE, "key3") .source(FakeIndexDefinition.newDoc(3))); + assertThat(req.toString()).isEqualTo( + "Bulk[1 delete request(s) on index fakes and type fake, 1 update request(s) on index fakes and type fake, 1 index request(s) on index fakes and type fake]"); + BulkResponse response = req.get(); assertThat(response.getItems()).hasSize(3); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/request/ProxyDeleteByQueryRequestBuilderTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/request/ProxyDeleteByQueryRequestBuilderTest.java index b43698ea882..e602a69ab21 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/es/request/ProxyDeleteByQueryRequestBuilderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/es/request/ProxyDeleteByQueryRequestBuilderTest.java @@ -49,8 +49,9 @@ public class ProxyDeleteByQueryRequestBuilderTest { @Test public void to_string() { - assertThat(esTester.client().prepareDeleteByQuery(FakeIndexDefinition.INDEX).toString()).isEqualTo("ES delete by query request on indices 'fakes'"); - assertThat(esTester.client().prepareDeleteByQuery().toString()).isEqualTo("ES delete by query request"); + assertThat(esTester.client().prepareDeleteByQuery(FakeIndexDefinition.INDEX).setQuery(QueryBuilders.matchAllQuery()).toString()).isEqualTo( + "ES delete by query request '{\"match_all\":{}}' on indices 'fakes'"); + assertThat(esTester.client().prepareDeleteByQuery().toString()).isEqualTo("ES delete by query request ''"); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleCreatorMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleCreatorMediumTest.java index 8b013c8e299..442236806a8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleCreatorMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleCreatorMediumTest.java @@ -20,10 +20,9 @@ package org.sonar.server.rule; -import org.sonar.server.search.BaseIndex; - import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; +import org.assertj.core.api.Fail; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; @@ -37,8 +36,10 @@ import org.sonar.core.rule.RuleDto; import org.sonar.core.rule.RuleDto.Format; import org.sonar.core.rule.RuleParamDto; import org.sonar.server.db.DbClient; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.rule.db.RuleDao; import org.sonar.server.rule.index.RuleIndex; +import org.sonar.server.search.BaseIndex; import org.sonar.server.tester.ServerTester; import java.util.List; @@ -142,7 +143,7 @@ public class RuleCreatorMediumTest { @Test public void create_custom_rule_with_no_parameter_value() throws Exception { // insert template rule - RuleDto templateRule = createTemplateRule(); + RuleDto templateRule = createTemplateRuleWithIntArrayParam(); NewRule newRule = NewRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) .setName("My custom") @@ -157,13 +158,60 @@ public class RuleCreatorMediumTest { assertThat(params).hasSize(1); RuleParamDto param = params.get(0); - assertThat(param.getName()).isEqualTo("regex"); - assertThat(param.getDescription()).isEqualTo("Reg ex"); - assertThat(param.getType()).isEqualTo("STRING"); + assertThat(param.getName()).isEqualTo("myIntegers"); + assertThat(param.getDescription()).isEqualTo("My Integers"); + assertThat(param.getType()).isEqualTo("INTEGER,multiple=true,values=1;2;3"); assertThat(param.getDefaultValue()).isNull(); } @Test + public void create_custom_rule_with_multiple_parameter_values() throws Exception { + // insert template rule + RuleDto templateRule = createTemplateRuleWithIntArrayParam(); + + NewRule newRule = NewRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) + .setName("My custom") + .setHtmlDescription("Some description") + .setSeverity(Severity.MAJOR) + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("myIntegers", "1,3")); + + RuleKey customRuleKey = creator.create(newRule); + dbSession.clearCache(); + + List<RuleParamDto> params = db.ruleDao().findRuleParamsByRuleKey(dbSession, customRuleKey); + assertThat(params).hasSize(1); + + RuleParamDto param = params.get(0); + assertThat(param.getName()).isEqualTo("myIntegers"); + assertThat(param.getDescription()).isEqualTo("My Integers"); + assertThat(param.getType()).isEqualTo("INTEGER,multiple=true,values=1;2;3"); + assertThat(param.getDefaultValue()).isEqualTo("1,3"); + } + + @Test + public void create_custom_rule_with_invalid_parameter() throws Exception { + // insert template rule + RuleDto templateRule = createTemplateRuleWithIntArrayParam(); + + // Create custom rule + NewRule newRule = NewRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) + .setName("My custom") + .setMarkdownDescription("Some description") + .setSeverity(Severity.MAJOR) + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("myIntegers", "1,polop,2")); + try { + creator.create(newRule); + Fail.failBecauseExceptionWasNotThrown(BadRequestException.class); + } catch (BadRequestException iae) { + assertThat(iae).hasMessage("errors.type.notInteger"); + } + + dbSession.clearCache(); + } + + @Test public void reactivate_custom_rule_if_already_exists_in_removed_status() throws Exception { String key = "CUSTOM_RULE"; @@ -614,4 +662,22 @@ public class RuleCreatorMediumTest { return templateRule; } + private RuleDto createTemplateRuleWithIntArrayParam() { + RuleDto templateRule = dao.insert(dbSession, + RuleTesting.newDto(RuleKey.of("java", "S002")) + .setIsTemplate(true) + .setLanguage("java") + .setConfigKey("S002") + .setDefaultSubCharacteristicId(1) + .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) + .setDefaultRemediationCoefficient("1h") + .setDefaultRemediationOffset("5min") + .setEffortToFixDescription("desc") + ); + RuleParamDto ruleParamDto = RuleParamDto.createFor(templateRule) + .setName("myIntegers").setType("INTEGER,multiple=true,values=1;2;3").setDescription("My Integers").setDefaultValue("1"); + dao.addRuleParam(dbSession, templateRule, ruleParamDto); + dbSession.commit(); + return templateRule; + } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/do_nothing_when_already_migrated.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/do_nothing_when_already_migrated.xml new file mode 100644 index 00000000000..b8ff37c389e --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/do_nothing_when_already_migrated.xml @@ -0,0 +1,53 @@ +<dataset> + + <characteristics id="1" kee="REUSABILITY" name="Reusability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + <characteristics id="2" kee="REUSABILITY_COMPLIANCE" name="Reusability Compliance" parent_id="1" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + <characteristics id="3" kee="PORTABILITY" name="Portability" parent_id="[null]" rule_id="[null]" characteristic_order="2" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + <characteristics id="4" kee="PORTABILITY_COMPLIANCE" name="Portability Compliance" parent_id="3" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + <characteristics id="5" kee="MAINTAINABILITY" name="Maintainability" parent_id="[null]" rule_id="[null]" characteristic_order="3" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + <characteristics id="6" kee="MAINTAINABILITY_COMPLIANCE" name="Maintainability Compliance" parent_id="5" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + <characteristics id="7" kee="SECURITY" name="Security" parent_id="[null]" rule_id="[null]" characteristic_order="4" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + <characteristics id="8" kee="SECURITY_COMPLIANCE" name="Security Compliance" parent_id="7" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2013-11-20" + updated_at="[null]"/> + + <characteristics id="9" kee="USABILITY" name="Usability" parent_id="[null]" rule_id="[null]" characteristic_order="5" enabled="[true]" created_at="2013-11-20" + updated_at="[null]"/> + <characteristics id="10" kee="USABILITY_ACCESSIBILITY" name="Accessibility" parent_id="9" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2013-11-20" + updated_at="[null]"/> + <characteristics id="11" kee="USABILITY_EASE_OF_USE" name="Ease of Use" parent_id="9" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2013-11-20" + updated_at="[null]"/> + <characteristics id="12" kee="USABILITY_COMPLIANCE" name="Usability Compliance" parent_id="9" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + <characteristics id="13" kee="EFFICIENCY" name="Efficiency" parent_id="[null]" rule_id="[null]" characteristic_order="6" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-20"/> + <characteristics id="14" kee="EFFICIENCY_COMPLIANCE" name="Efficiency Compliance" parent_id="13" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + <characteristics id="15" kee="CHANGEABILITY" name="Changeability" parent_id="[null]" rule_id="[null]" characteristic_order="7" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-20"/> + <characteristics id="16" kee="CHANGEABILITY_COMPLIANCE" name="Changeability Compliance" parent_id="15" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + <characteristics id="17" kee="RELIABILITY" name="Reliability" parent_id="[null]" rule_id="[null]" characteristic_order="8" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-20"/> + <characteristics id="18" kee="RELIABILITY_COMPLIANCE" name="Reliability Compliance" parent_id="17" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + <characteristics id="19" kee="TESTABILITY" name="Testability" parent_id="[null]" rule_id="[null]" characteristic_order="9" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-20"/> + <characteristics id="20" kee="TESTABILITY_COMPLIANCE" name="Testability Compliance" parent_id="19" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/empty.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/empty.xml new file mode 100644 index 00000000000..871dedcb5e9 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/empty.xml @@ -0,0 +1,3 @@ +<dataset> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_compliance_already_exists_as_characteristic.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_compliance_already_exists_as_characteristic.xml new file mode 100644 index 00000000000..1f2f39bdbef --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_compliance_already_exists_as_characteristic.xml @@ -0,0 +1,9 @@ +<dataset> + + <characteristics id="1" kee="REUSABILITY" name="Reusability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="2" kee="REUSABILITY_COMPLIANCE" name="Compliance" parent_id="[null]" rule_id="[null]" characteristic_order="2" enabled="[true]" created_at="2013-11-20" + updated_at="[null]"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_compliance_already_exists_under_wrong_characteristic.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_compliance_already_exists_under_wrong_characteristic.xml new file mode 100644 index 00000000000..3dbfebd4153 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_compliance_already_exists_under_wrong_characteristic.xml @@ -0,0 +1,13 @@ +<dataset> + + <characteristics id="1" kee="REUSABILITY" name="Reusability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="2" kee="PORTABILITY" name="Portability" parent_id="[null]" rule_id="[null]" characteristic_order="2" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="3" kee="REUSABILITY_COMPLIANCE" name="Reusability Compliance" parent_id="2" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" + updated_at="[null]"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_usability_exists_as_sub_characteristic.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_usability_exists_as_sub_characteristic.xml new file mode 100644 index 00000000000..70016cc2d9d --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/fail_if_usability_exists_as_sub_characteristic.xml @@ -0,0 +1,9 @@ +<dataset> + + <characteristics id="1" kee="REUSABILITY" name="Reusability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="2" kee="USABILITY" name="Usability" parent_id="1" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/insert_usability_at_the_top_if_security_does_exists-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/insert_usability_at_the_top_if_security_does_exists-result.xml new file mode 100644 index 00000000000..4f596203e6d --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/insert_usability_at_the_top_if_security_does_exists-result.xml @@ -0,0 +1,30 @@ +<dataset> + + <!-- Oder has changed : this characteristic is now one step lower --> + <characteristics id="1" kee="REUSABILITY" name="Reusability" parent_id="[null]" rule_id="[null]" characteristic_order="2" enabled="[true]" created_at="2013-11-20" + updated_at="2015-02-15"/> + + <!-- Oder has changed : this characteristic is now one step lower --> + <characteristics id="2" kee="PORTABILITY" name="Portability" parent_id="[null]" rule_id="[null]" characteristic_order="3" enabled="[true]" created_at="2013-11-20" + updated_at="2015-02-15"/> + + <!-- New characteristic 'Usability' is on the top (order 1) --> + <characteristics id="3" kee="USABILITY" name="Usability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + <characteristics id="4" kee="USABILITY_ACCESSIBILITY" name="Accessibility" parent_id="3" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + <characteristics id="5" kee="USABILITY_EASE_OF_USE" name="Ease of Use" parent_id="3" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + <characteristics id="6" kee="USABILITY_COMPLIANCE" name="Usability Compliance" parent_id="3" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + + <!-- New sub characteristic 'Compliance' under Reusability --> + <characteristics id="7" kee="REUSABILITY_COMPLIANCE" name="Reusability Compliance" parent_id="1" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Portability --> + <characteristics id="8" kee="PORTABILITY_COMPLIANCE" name="Portability Compliance" parent_id="2" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/insert_usability_at_the_top_if_security_does_exists.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/insert_usability_at_the_top_if_security_does_exists.xml new file mode 100644 index 00000000000..7ab9a9726ac --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/insert_usability_at_the_top_if_security_does_exists.xml @@ -0,0 +1,9 @@ +<dataset> + + <characteristics id="1" kee="REUSABILITY" name="Reusability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="2" kee="PORTABILITY" name="Portability" parent_id="[null]" rule_id="[null]" characteristic_order="2" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/migrate-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/migrate-result.xml new file mode 100644 index 00000000000..91f1c6ab778 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/migrate-result.xml @@ -0,0 +1,74 @@ +<dataset> + + <characteristics id="1" kee="REUSABILITY" name="Reusability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="2" kee="PORTABILITY" name="Portability" parent_id="[null]" rule_id="[null]" characteristic_order="2" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="3" kee="MAINTAINABILITY" name="Maintainability" parent_id="[null]" rule_id="[null]" characteristic_order="3" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="4" kee="SECURITY" name="Security" parent_id="[null]" rule_id="[null]" characteristic_order="4" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <!-- Oder has changed : this characteristic is now one step lower --> + <characteristics id="5" kee="EFFICIENCY" name="Efficiency" parent_id="[null]" rule_id="[null]" characteristic_order="6" enabled="[true]" created_at="2013-11-20" + updated_at="2015-02-15"/> + + <!-- Oder has changed : this characteristic is now one step lower --> + <characteristics id="6" kee="CHANGEABILITY" name="Changeability" parent_id="[null]" rule_id="[null]" characteristic_order="7" enabled="[true]" created_at="2013-11-20" + updated_at="2015-02-15"/> + + <!-- Oder has changed : this characteristic is now one step lower --> + <characteristics id="7" kee="RELIABILITY" name="Reliability" parent_id="[null]" rule_id="[null]" characteristic_order="8" enabled="[true]" created_at="2013-11-20" + updated_at="2015-02-15"/> + + <!-- Oder has changed : this characteristic is now one step lower --> + <characteristics id="8" kee="TESTABILITY" name="Testability" parent_id="[null]" rule_id="[null]" characteristic_order="9" enabled="[true]" created_at="2013-11-20" + updated_at="2015-02-15"/> + + <!-- New characteristic 'Usability' is after Security --> + <characteristics id="9" kee="USABILITY" name="Usability" parent_id="[null]" rule_id="[null]" characteristic_order="5" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + <!-- New sub characteristics under Usability --> + <characteristics id="10" kee="USABILITY_ACCESSIBILITY" name="Accessibility" parent_id="9" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + <characteristics id="11" kee="USABILITY_EASE_OF_USE" name="Ease of Use" parent_id="9" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + <characteristics id="12" kee="USABILITY_COMPLIANCE" name="Usability Compliance" parent_id="9" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Reusability --> + <characteristics id="13" kee="REUSABILITY_COMPLIANCE" name="Reusability Compliance" parent_id="1" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Portability --> + <characteristics id="14" kee="PORTABILITY_COMPLIANCE" name="Portability Compliance" parent_id="2" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Maintainability --> + <characteristics id="15" kee="MAINTAINABILITY_COMPLIANCE" name="Maintainability Compliance" parent_id="3" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Security --> + <characteristics id="16" kee="SECURITY_COMPLIANCE" name="Security Compliance" parent_id="4" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Efficiency --> + <characteristics id="17" kee="EFFICIENCY_COMPLIANCE" name="Efficiency Compliance" parent_id="5" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Changeability --> + <characteristics id="18" kee="CHANGEABILITY_COMPLIANCE" name="Changeability Compliance" parent_id="6" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Reliability --> + <characteristics id="19" kee="RELIABILITY_COMPLIANCE" name="Reliability Compliance" parent_id="7" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Testability --> + <characteristics id="20" kee="TESTABILITY_COMPLIANCE" name="Testability Compliance" parent_id="8" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/migrate.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/migrate.xml new file mode 100644 index 00000000000..2d92c7cc413 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/migrate.xml @@ -0,0 +1,27 @@ +<dataset> + + <characteristics id="1" kee="REUSABILITY" name="Reusability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="2" kee="PORTABILITY" name="Portability" parent_id="[null]" rule_id="[null]" characteristic_order="2" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="3" kee="MAINTAINABILITY" name="Maintainability" parent_id="[null]" rule_id="[null]" characteristic_order="3" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="4" kee="SECURITY" name="Security" parent_id="[null]" rule_id="[null]" characteristic_order="4" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="5" kee="EFFICIENCY" name="Efficiency" parent_id="[null]" rule_id="[null]" characteristic_order="5" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="6" kee="CHANGEABILITY" name="Changeability" parent_id="[null]" rule_id="[null]" characteristic_order="6" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="7" kee="RELIABILITY" name="Reliability" parent_id="[null]" rule_id="[null]" characteristic_order="7" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="8" kee="TESTABILITY" name="Testability" parent_id="[null]" rule_id="[null]" characteristic_order="8" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/not_fail_if_some_deprecated_requirements_still_exists_in_db.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/not_fail_if_some_deprecated_requirements_still_exists_in_db.xml new file mode 100644 index 00000000000..6953f5230bb --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/not_fail_if_some_deprecated_requirements_still_exists_in_db.xml @@ -0,0 +1,14 @@ +<dataset> + + <characteristics id="1" kee="USABILITY" name="Usability" parent_id="[null]" rule_id="[null]" characteristic_order="1" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + <characteristics id="2" kee="USABILITY_ACCESSIBILITY" name="Accessibility" parent_id="1" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2013-11-20" + updated_at="[null]"/> + <characteristics id="3" kee="USABILITY_EASE_OF_USE" name="Ease of Use" parent_id="1" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2013-11-20" + updated_at="[null]"/> + <characteristics id="4" kee="USABILITY_COMPLIANCE" name="Usability Compliance" parent_id="1" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2013-11-20" updated_at="[null]"/> + + <characteristics id="5" kee="[null]" name="[null]" parent_id="3" rule_id="3" characteristic_order="[null]" enabled="[true]" created_at="2013-11-20" updated_at="2013-11-22"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/schema.sql b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/schema.sql new file mode 100644 index 00000000000..98c025def6b --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/schema.sql @@ -0,0 +1,11 @@ +CREATE TABLE "CHARACTERISTICS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(100), + "NAME" VARCHAR(100), + "PARENT_ID" INTEGER, + "RULE_ID" INTEGER, + "CHARACTERISTIC_ORDER" INTEGER, + "ENABLED" BOOLEAN, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/update_usability_if_already_exists-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/update_usability_if_already_exists-result.xml new file mode 100644 index 00000000000..1b12df0872e --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/update_usability_if_already_exists-result.xml @@ -0,0 +1,30 @@ +<dataset> + + <characteristics id="1" kee="SECURITY" name="Security" parent_id="[null]" rule_id="[null]" characteristic_order="4" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <!-- Oder has changed : this characteristic is now one step lower --> + <characteristics id="2" kee="EFFICIENCY" name="Efficiency" parent_id="[null]" rule_id="[null]" characteristic_order="6" enabled="[true]" created_at="2013-11-20" + updated_at="2015-02-15"/> + + <!-- Usability is moved after Security --> + <characteristics id="3" kee="USABILITY" name="Usability" parent_id="[null]" rule_id="[null]" characteristic_order="5" enabled="[true]" created_at="2013-11-20" + updated_at="2015-02-15"/> + + <!-- New sub characteristics under Usability --> + <characteristics id="4" kee="USABILITY_ACCESSIBILITY" name="Accessibility" parent_id="3" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + <characteristics id="5" kee="USABILITY_EASE_OF_USE" name="Ease of Use" parent_id="3" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + <characteristics id="6" kee="USABILITY_COMPLIANCE" name="Usability Compliance" parent_id="3" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Security --> + <characteristics id="7" kee="SECURITY_COMPLIANCE" name="Security Compliance" parent_id="1" rule_id="[null]" characteristic_order="[null]" enabled="[true]" created_at="2015-02-15" + updated_at="[null]"/> + + <!-- New sub characteristic 'Compliance' under Efficiency --> + <characteristics id="8" kee="EFFICIENCY_COMPLIANCE" name="Efficiency Compliance" parent_id="2" rule_id="[null]" characteristic_order="[null]" enabled="[true]" + created_at="2015-02-15" updated_at="[null]"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/update_usability_if_already_exists.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/update_usability_if_already_exists.xml new file mode 100644 index 00000000000..33d901de062 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigrationTest/update_usability_if_already_exists.xml @@ -0,0 +1,13 @@ +<dataset> + + <characteristics id="1" kee="SECURITY" name="Security" parent_id="[null]" rule_id="[null]" characteristic_order="4" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <characteristics id="2" kee="EFFICIENCY" name="Efficiency" parent_id="[null]" rule_id="[null]" characteristic_order="5" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + + <!-- Usability should be move after Security --> + <characteristics id="3" kee="USABILITY" name="Usability" parent_id="[null]" rule_id="[null]" characteristic_order="6" enabled="[true]" created_at="2013-11-20" + updated_at="2013-11-22"/> + +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest/before.xml b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest/before.xml new file mode 100644 index 00000000000..52ad14d35b2 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest/before.xml @@ -0,0 +1,28 @@ +<dataset> + <!-- new migration --> + <events + id="1" + created_at="2014-09-25" + created_at_ms="[null]" + event_date="2014-09-25" + event_date_ms="[null]" + /> + + <!-- re-entrant migration - ignore the ones that are already fed with new dates --> + <events + id="2" + created_at="2014-09-25" + created_at_ms="1500000000" + event_date="2014-09-25" + event_date_ms="1500000000" + /> + + <!-- NULL dates --> + <events + id="3" + created_at="[null]" + created_at_ms="[null]" + event_date="[null]" + event_date_ms="[null]" + /> +</dataset> diff --git a/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest/schema.sql b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest/schema.sql new file mode 100644 index 00000000000..71ac42d40ef --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/db/migrations/v51/FeedEventsLongDatesTest/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE "EVENTS" ( + "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "CREATED_AT" TIMESTAMP, + "CREATED_AT_MS" BIGINT, + "EVENT_DATE" TIMESTAMP, + "EVENT_DATE_MS" BIGINT +); diff --git a/server/sonar-web/src/main/coffee/issue/issue-view.coffee b/server/sonar-web/src/main/coffee/issue/issue-view.coffee index 622ff88cd98..e0028cf404b 100644 --- a/server/sonar-web/src/main/coffee/issue/issue-view.coffee +++ b/server/sonar-web/src/main/coffee/issue/issue-view.coffee @@ -201,7 +201,7 @@ define [ assignToMe: -> - view = new AssignFormView model: @model + view = new AssignFormView model: @model, triggerEl: $('body') view.submit window.SS.user, window.SS.userName view.close() diff --git a/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee index 42acf9397f9..f9c20fbb737 100644 --- a/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee +++ b/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee @@ -18,6 +18,8 @@ define [ 'click .js-select-period-start': 'selectPeriodStart' 'click .js-select-period-end': 'selectPeriodEnd' + 'click .sonar-d3 rect': 'selectBar' + 'click .js-all': 'onAllClick' 'click .js-last-week': 'onLastWeekClick' 'click .js-last-month': 'onLastMonthClick' @@ -70,6 +72,12 @@ define [ @options.app.state.updateFilter createdAfter: null, createdBefore: null, createdAt: null + selectBar: (e) -> + periodStart = $(e.currentTarget).data 'period-start' + periodEnd = $(e.currentTarget).data 'period-end' + @options.app.state.updateFilter createdAfter: periodStart, createdBefore: periodEnd, createdAt: null + + onAllClick: -> @disable() diff --git a/server/sonar-web/src/main/coffee/issues/models/state.coffee b/server/sonar-web/src/main/coffee/issues/models/state.coffee index b16dea09c72..1cbaafeb8b6 100644 --- a/server/sonar-web/src/main/coffee/issues/models/state.coffee +++ b/server/sonar-web/src/main/coffee/issues/models/state.coffee @@ -7,10 +7,10 @@ define [ page: 1 maxResultsReached: false query: {} - facets: ['severities', 'resolutions', 'createdAt', 'rules', 'tags', 'projectUuids'] + facets: ['severities', 'resolutions'] isContext: false - allFacets: ['issues', 'severities', 'resolutions', 'createdAt', 'rules', 'tags', 'statuses', 'projectUuids', + allFacets: ['issues', 'severities', 'resolutions', 'statuses', 'createdAt', 'rules', 'tags', 'projectUuids', 'moduleUuids', 'directories', 'fileUuids', 'assignees', 'reporters', 'authors', 'languages', 'actionPlans'], facetsFromServer: ['severities', 'statuses', 'resolutions', 'actionPlans', 'projectUuids', 'directories', 'rules', diff --git a/server/sonar-web/src/main/coffee/quality-gate/app.coffee b/server/sonar-web/src/main/coffee/quality-gate/app.coffee index 28cf9daf416..1e376e3b2b5 100644 --- a/server/sonar-web/src/main/coffee/quality-gate/app.coffee +++ b/server/sonar-web/src/main/coffee/quality-gate/app.coffee @@ -18,10 +18,6 @@ requirejs [ QualityGateLayout ) -> - # Add html class to mark the page as navigator page - jQuery('html').addClass('navigator-page quality-gates-page'); - - # Create a Quality Gate Application App = new Marionette.Application @@ -49,8 +45,8 @@ requirejs [ # Construct layout App.addInitializer -> @layout = new QualityGateLayout app: @ - jQuery('#content').append @layout.render().el - @layout.onResize() + jQuery('#quality-gates').append @layout.render().el + jQuery('#footer').addClass 'search-navigator-footer' # Construct actions bar @@ -101,8 +97,5 @@ requirejs [ jQuery.when(qualityGatesXHR, appXHR, l10nXHR) .done -> - # Remove the initial spinner - jQuery('#quality-gate-page-loader').remove() - # Start the application App.start() diff --git a/server/sonar-web/src/main/coffee/quality-gate/layout.coffee b/server/sonar-web/src/main/coffee/quality-gate/layout.coffee index 9a982cf1e02..4e58fa172e8 100644 --- a/server/sonar-web/src/main/coffee/quality-gate/layout.coffee +++ b/server/sonar-web/src/main/coffee/quality-gate/layout.coffee @@ -2,45 +2,21 @@ define [ 'templates/quality-gates' ], -> + $ = jQuery + class AppLayout extends Marionette.Layout - className: 'navigator quality-gates-navigator' template: Templates['quality-gates-layout'] regions: - headerRegion: '.navigator-header' - actionsRegion: '.navigator-actions' - resultsRegion: '.navigator-results' - detailsRegion: '.navigator-details' - - - initialize: (options) -> - @listenTo options.app.qualityGates, 'all', @updateLayout - jQuery(window).on 'resize', => @onResize() - - - updateLayout: -> - empty = @options.app.qualityGates.length == 0 - @$(@headerRegion.el).toggle !empty - @$(@detailsRegion.el).toggle !empty - - - onResize: -> - footerEl = jQuery('#footer') - footerHeight = footerEl.outerHeight true - - resultsEl = jQuery('.navigator-results') - resultsHeight = jQuery(window).height() - resultsEl.offset().top - - parseInt(resultsEl.css('margin-bottom'), 10) - footerHeight - resultsEl.height resultsHeight - - detailsEl = jQuery('.navigator-details') - detailsWidth = jQuery(window).width() - detailsEl.offset().left - - parseInt(detailsEl.css('margin-right'), 10) - detailsHeight = jQuery(window).height() - detailsEl.offset().top - - parseInt(detailsEl.css('margin-bottom'), 10) - footerHeight - detailsEl.width(detailsWidth).height detailsHeight + headerRegion: '.search-navigator-workspace-header' + actionsRegion: '.search-navigator-filters' + resultsRegion: '.quality-gates-results' + detailsRegion: '.search-navigator-workspace-list' onRender: -> - @updateLayout() + $('.search-navigator').addClass 'sticky' + top = $('.search-navigator').offset().top + @$('.search-navigator-workspace-header').css top: top + @$('.search-navigator-side').css({ top: top }).isolatedScroll() diff --git a/server/sonar-web/src/main/coffee/quality-gate/router.coffee b/server/sonar-web/src/main/coffee/quality-gate/router.coffee index 9bb56410f9a..e185428a51b 100644 --- a/server/sonar-web/src/main/coffee/quality-gate/router.coffee +++ b/server/sonar-web/src/main/coffee/quality-gate/router.coffee @@ -34,7 +34,5 @@ define [ @app.layout.detailsRegion.show qualityGateDetailView qualityGateDetailView.$el.hide() - qualityGateDetailHeaderView.showSpinner() qualityGate.fetch().done -> qualityGateDetailView.$el.show() - qualityGateDetailHeaderView.hideSpinner() diff --git a/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-detail-header-view.coffee b/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-detail-header-view.coffee index 3bcc07e9eaf..a90b5580f2a 100644 --- a/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-detail-header-view.coffee +++ b/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-detail-header-view.coffee @@ -49,26 +49,21 @@ define [ yesLabel: t 'delete' noLabel: t 'cancel' yesHandler: => - @showSpinner() jQuery.ajax type: 'POST' url: "#{baseUrl}/api/qualitygates/destroy" data: id: @model.id - .always => @hideSpinner() .done => @options.app.deleteQualityGate @model.id always: => @ui.deleteButton.blur() changeDefault: (set) -> - @showSpinner() data = if set then { id: @model.id } else {} method = if set then 'set_as_default' else 'unset_default' jQuery.ajax type: 'POST' url: "#{baseUrl}/api/qualitygates/#{method}" data: data - .always => - @hideSpinner() .done => @options.app.unsetDefaults @model.id @model.set 'default', !@model.get('default') @@ -82,15 +77,5 @@ define [ @changeDefault false - showSpinner: -> - @$el.hide() - jQuery(@spinner).insertBefore @$el - - - hideSpinner: -> - @$el.prev().remove() - @$el.show() - - serializeData: -> _.extend super, canEdit: @options.app.canEdit diff --git a/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-sidebar-list-item-view.coffee b/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-sidebar-list-item-view.coffee index d2c618fa745..4f2e3eaa14c 100644 --- a/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-sidebar-list-item-view.coffee +++ b/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-sidebar-list-item-view.coffee @@ -3,7 +3,7 @@ define [ ], -> class QualityGateSidebarListItemView extends Marionette.ItemView - tagName: 'li' + className: 'facet search-navigator-facet' template: Templates['quality-gate-sidebar-list-item'] diff --git a/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-sidebar-list-view.coffee b/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-sidebar-list-view.coffee index 063fd569a2c..7cdfbe4a14e 100644 --- a/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-sidebar-list-view.coffee +++ b/server/sonar-web/src/main/coffee/quality-gate/views/quality-gate-sidebar-list-view.coffee @@ -7,8 +7,7 @@ define [ ) -> class QualityGateSidebarListView extends Marionette.CollectionView - tagName: 'ol' - className: 'navigator-results-list' + className: 'search-navigator-facet-list' itemView: QualityGateSidebarListItemView emptyView: QualityGateSidebarListEmptyView diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-workspace-list-item.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-workspace-list-item.hbs index 7db42af3919..75f4c415e31 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-workspace-list-item.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-workspace-list-item.hbs @@ -23,7 +23,7 @@ <td class="coding-rule-table-meta-cell"> <div class="coding-rule-meta"> {{#notEq status 'READY'}} - {{status}} + <span class="text-danger">{{status}}</span> {{/notEq}} <a class="js-lang link-no-underline" data-lang="{{lang}}">{{langName}}</a> diff --git a/server/sonar-web/src/main/hbs/issues/issues-filters.hbs b/server/sonar-web/src/main/hbs/issues/issues-filters.hbs index d3e85908014..ee0f442ae96 100644 --- a/server/sonar-web/src/main/hbs/issues/issues-filters.hbs +++ b/server/sonar-web/src/main/hbs/issues/issues-filters.hbs @@ -23,12 +23,25 @@ <div class="button-group"> {{#if state.canManageFilters}} {{#if filter.canModify}} - {{#if state.changed}}<button class="js-filter-save" id="issues-filter-save">{{t 'save'}}</button>{{/if}} + {{#if state.changed}} + <button class="js-filter-save" id="issues-filter-save">{{t 'save'}}</button> + {{/if}} {{/if}} - {{#unless filter.id}}<button class="js-filter-save-as" id="issues-filter-save-as">{{t 'save_as'}}</button>{{/unless}} - {{#if filter.id}}<button class="js-filter-copy" id="issues-filter-copy">{{t 'copy'}}</button>{{/if}} + + {{#unless filter.id}} + <button class="js-filter-save-as" id="issues-filter-save-as">{{t 'save_as'}}</button> + {{/unless}} + + {{#if filter.id}} + {{#unless state.changed}} + <button class="js-filter-copy" id="issues-filter-copy">{{t 'copy'}}</button> + {{/unless}} + {{/if}} + {{#if filter.canModify}} - {{#if filter.id}}<button class="js-filter-edit" id="issues-filter-edit">{{t 'edit'}}</button>{{/if}} + {{#if filter.id}} + <button class="js-filter-edit" id="issues-filter-edit">{{t 'edit'}}</button> + {{/if}} {{/if}} {{/if}} </div> diff --git a/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs b/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs index 21158f33975..e29177268b7 100644 --- a/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs +++ b/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs @@ -1,6 +1,12 @@ <div class="issues-header-component"> {{#if state.component}} - <a class="js-back">{{t 'issues.return_to_list'}}</a> + <a class="js-back">{{t 'issues.return_to_list'}}</a> + + {{#with state.component}} + {{qualifierIcon 'TRK'}} <a href="{{dashboardUrl project}}" title="{{projectName}}">{{projectName}}</a> + + {{qualifierIcon qualifier}} <a href="{{dashboardUrl key}}" title="{{name}}">{{name}}</a> + {{/with}} {{else}} {{/if}} diff --git a/server/sonar-web/src/main/hbs/quality-gates/quality-gate-detail-condition.hbs b/server/sonar-web/src/main/hbs/quality-gates/quality-gate-detail-condition.hbs index 79973fe9a38..9be26807f78 100644 --- a/server/sonar-web/src/main/hbs/quality-gates/quality-gate-detail-condition.hbs +++ b/server/sonar-web/src/main/hbs/quality-gates/quality-gate-detail-condition.hbs @@ -27,19 +27,19 @@ {{t 'quality_gates.operator' op}} {{/if}} </td> -<td width="15%" nowrap="nowrap"> +<td width="1" class="nowrap"> <i class="icon-alert-warn" title="{{t 'quality_gates.warning_tooltip'}}"></i> {{#if canEdit}} - <input name="warning" class="measure-input" data-type="{{metric.type}}" placeholder="{{metric.placeholder}}" + <input name="warning" class="input-small" data-type="{{metric.type}}" placeholder="{{metric.placeholder}}" type="text"> {{else}} {{warning}} {{/if}} </td> -<td width="15%" nowrap="nowrap"> +<td width="1" class="nowrap"> <i class="icon-alert-error" title="{{t 'quality_gates.error_tooltip'}}"></i> {{#if canEdit}} - <input name="error" class="measure-input" data-type="{{metric.type}}" placeholder="{{metric.placeholder}}" + <input name="error" class="input-small" data-type="{{metric.type}}" placeholder="{{metric.placeholder}}" type="text"> {{else}} {{error}} diff --git a/server/sonar-web/src/main/hbs/quality-gates/quality-gate-detail-header.hbs b/server/sonar-web/src/main/hbs/quality-gates/quality-gate-detail-header.hbs index e84a730d379..71f7e3b06de 100644 --- a/server/sonar-web/src/main/hbs/quality-gates/quality-gate-detail-header.hbs +++ b/server/sonar-web/src/main/hbs/quality-gates/quality-gate-detail-header.hbs @@ -1,14 +1,16 @@ -<h1 class="navigator-header-title">{{name}}</h1> +<h2 class="search-navigator-header-component">{{name}}</h2> {{#if canEdit}} - <div class="navigator-header-actions button-group"> - <button id="quality-gate-rename">{{t 'rename'}}</button> - <button id="quality-gate-copy">{{t 'copy'}}</button> - {{#if default}} - <button id="quality-gate-unset-as-default">{{t 'unset_as_default'}}</button> - {{else}} - <button id="quality-gate-set-as-default">{{t 'set_as_default'}}</button> - {{/if}} - <button id="quality-gate-delete" class="button-red">{{t 'delete'}}</button> + <div class="search-navigator-header-actions"> + <div class="button-group"> + <button id="quality-gate-rename">{{t 'rename'}}</button> + <button id="quality-gate-copy">{{t 'copy'}}</button> + {{#if default}} + <button id="quality-gate-unset-as-default">{{t 'unset_as_default'}}</button> + {{else}} + <button id="quality-gate-set-as-default">{{t 'set_as_default'}}</button> + {{/if}} + <button id="quality-gate-delete" class="button-red">{{t 'delete'}}</button> + </div> </div> -{{/if}}
\ No newline at end of file +{{/if}} diff --git a/server/sonar-web/src/main/hbs/quality-gates/quality-gate-sidebar-list-item.hbs b/server/sonar-web/src/main/hbs/quality-gates/quality-gate-sidebar-list-item.hbs index af8d93ae07b..2b8f1089eb7 100644 --- a/server/sonar-web/src/main/hbs/quality-gates/quality-gate-sidebar-list-item.hbs +++ b/server/sonar-web/src/main/hbs/quality-gates/quality-gate-sidebar-list-item.hbs @@ -1 +1,7 @@ -<div class="line line-nowrap">{{name}} {{#if default}}<span class="subtitle">({{t 'default'}})</span>{{/if}}</div>
\ No newline at end of file +<span class="facet-name"> + {{name}} +</span> + +{{#if default}} + <span class="facet-stat">{{t 'default'}}</span> +{{/if}} diff --git a/server/sonar-web/src/main/hbs/quality-gates/quality-gates-layout.hbs b/server/sonar-web/src/main/hbs/quality-gates/quality-gates-layout.hbs index 22f6fba05e7..d596459b6a7 100644 --- a/server/sonar-web/src/main/hbs/quality-gates/quality-gates-layout.hbs +++ b/server/sonar-web/src/main/hbs/quality-gates/quality-gates-layout.hbs @@ -1,10 +1,9 @@ -<div class="navigator-content"> - <div class="navigator-side"> - <div class="navigator-actions"></div> - <div class="navigator-results quality-gates-nav"></div> - </div> - <div class="navigator-main"> - <div class="navigator-header"></div> - <div class="navigator-details"></div> - </div> -</div>
\ No newline at end of file +<div class="search-navigator-side quality-gates-side"> + <div class="search-navigator-filters"></div> + <div class="quality-gates-results"></div> +</div> + +<div class="search-navigator-workspace"> + <div class="search-navigator-workspace-header"></div> + <div class="search-navigator-workspace-list"></div> +</div> diff --git a/server/sonar-web/src/main/hbs/source-viewer/source-viewer.hbs b/server/sonar-web/src/main/hbs/source-viewer/source-viewer.hbs index 6a73836e5a3..0f239f255df 100644 --- a/server/sonar-web/src/main/hbs/source-viewer/source-viewer.hbs +++ b/server/sonar-web/src/main/hbs/source-viewer/source-viewer.hbs @@ -18,19 +18,20 @@ </td> <td class="source-meta source-line-coverage {{#notNull coverageStatus}}source-line-{{coverageStatus}}{{/notNull}}" - data-line-number="{{line}}"> + data-line-number="{{line}}" {{#notNull coverageStatus}}title="{{t 'source_viewer.tooltip' coverageStatus}}" data-placement="right" data-toggle="tooltip"{{/notNull}}> <div class="source-line-bar"></div> </td> {{#if ../hasDuplications}} <td class="source-meta source-line-duplications {{#if duplicated}}source-line-duplicated{{/if}}" - title="{{t 'source_viewer.expand_duplications'}}"> + {{#if duplicated}}title="{{t 'source_viewer.tooltip.duplicated_line'}}" data-placement="right" data-toggle="tooltip"{{/if}}> <div class="source-line-bar"></div> </td> {{#each duplications}} <td class="source-meta source-line-duplications-extra {{#if this}}source-line-duplicated{{/if}}" - data-index="{{this}}" data-line-number="{{line}}"> + data-index="{{this}}" data-line-number="{{../line}}" + {{#if this}}title="{{t 'source_viewer.tooltip.duplicated_block'}}" data-placement="right" data-toggle="tooltip"{{/if}}> <div class="source-line-bar"></div> </td> {{/each}} @@ -43,6 +44,10 @@ {{/withFirst}} </td> + <td class="source-meta source-line-filtered-container" data-line-number="{{line}}"> + <div class="source-line-bar"></div> + </td> + <td class="source-line-code code {{#notEmpty issues}}has-issues{{/notEmpty}}" data-line-number="{{line}}"> {{#notNull code}} <pre>{{#if code}}{{{code}}}{{else}} {{/if}}</pre> diff --git a/server/sonar-web/src/main/js/coding-rules/controller.js b/server/sonar-web/src/main/js/coding-rules/controller.js index 91e98b84639..627b7e3ec66 100644 --- a/server/sonar-web/src/main/js/coding-rules/controller.js +++ b/server/sonar-web/src/main/js/coding-rules/controller.js @@ -1,7 +1,7 @@ define([ - 'components/navigator/controller', - 'coding-rules/models/rule', - 'coding-rules/rule-details-view' + 'components/navigator/controller', + 'coding-rules/models/rule', + 'coding-rules/rule-details-view' ], function (Controller, Rule, RuleDetailsView) { var $ = jQuery; @@ -20,14 +20,16 @@ define([ fields.push('isTemplate'); fields.push('severity'); } - return { + var params = { p: this.app.state.get('page'), ps: this.pageSize, facets: this._facetsFromServer().join(), - f: fields.join(), - s: 'name', - asc: true + f: fields.join() }; + if (this.app.state.get('query').q == null) { + _.extend(params, { s: 'name', asc: true }); + } + return params; }, fetchList: function (firstPage) { diff --git a/server/sonar-web/src/main/js/coding-rules/rule/profile-activation-view.js b/server/sonar-web/src/main/js/coding-rules/rule/profile-activation-view.js index 1de7444ac85..2d2f1f85730 100644 --- a/server/sonar-web/src/main/js/coding-rules/rule/profile-activation-view.js +++ b/server/sonar-web/src/main/js/coding-rules/rule/profile-activation-view.js @@ -29,7 +29,8 @@ define([ minimumResultsForSearch: 5 }); - var format = function (state) { + var that = this, + format = function (state) { if (!state.id) { return state.text; } else { @@ -44,6 +45,9 @@ define([ formatResult: format, formatSelection: format }); + setTimeout(function () { + that.$('a').first().focus(); + }, 0); }, activate: function (e) { diff --git a/server/sonar-web/src/main/js/coding-rules/workspace-list-item-view.js b/server/sonar-web/src/main/js/coding-rules/workspace-list-item-view.js index 56937b8add9..eb1e49a5a11 100644 --- a/server/sonar-web/src/main/js/coding-rules/workspace-list-item-view.js +++ b/server/sonar-web/src/main/js/coding-rules/workspace-list-item-view.js @@ -14,6 +14,7 @@ define([ events: { 'click': 'selectCurrent', + 'dblclick': 'openRule', 'click .js-rule': 'openRule', 'click .coding-rules-detail-quality-profile-activate': 'activate', 'click .coding-rules-detail-quality-profile-change': 'change', diff --git a/server/sonar-web/src/main/js/drilldown/app.js b/server/sonar-web/src/main/js/drilldown/app.js index 45ebac66d20..85be33f8b90 100644 --- a/server/sonar-web/src/main/js/drilldown/app.js +++ b/server/sonar-web/src/main/js/drilldown/app.js @@ -24,7 +24,7 @@ requirejs([ viewer.open(uuid); if (window.drilldown.period != null) { viewer.on('loaded', function () { - viewer.filterLinesByDate(window.drilldown.period); + viewer.filterLinesByDate(window.drilldown.period, window.drilldown.periodName); }); } }); diff --git a/server/sonar-web/src/main/js/graphics/barchart.js b/server/sonar-web/src/main/js/graphics/barchart.js index 5b51819bad4..b96ca95f901 100644 --- a/server/sonar-web/src/main/js/graphics/barchart.js +++ b/server/sonar-web/src/main/js/graphics/barchart.js @@ -74,10 +74,18 @@ .attr('height', function (d) { return Math.floor(yScale(d.count)); }) + .style('cursor', 'pointer') + .attr('data-period-start', function (d) { + return moment(d.val).format('YYYY-MM-DD'); + }) + .attr('data-period-end', function (d, i) { + var ending = i < data.length - 1 ? moment(data[i + 1].val).subtract(1, 'seconds') : moment(); + return ending.format('YYYY-MM-DD'); + }) .attr('title', function (d, i) { var beginning = moment(d.val), ending = i < data.length - 1 ? moment(data[i + 1].val).subtract(1, 'days') : moment(); - return d.count + ' | ' + beginning.format('LL') + ' - ' + ending.format('LL'); + return d.count + '<br>' + beginning.format('LL') + ' – ' + ending.format('LL'); }) .attr('data-placement', 'right') .attr('data-toggle', 'tooltip'); @@ -97,7 +105,7 @@ return text; }); - $(this).find('[data-toggle=tooltip]').tooltip({ container: 'body' }); + $(this).find('[data-toggle=tooltip]').tooltip({ container: 'body', html: true }); } }); }; diff --git a/server/sonar-web/src/main/js/nav/app.js b/server/sonar-web/src/main/js/nav/app.js index 36e52658a1d..7945b6274bb 100644 --- a/server/sonar-web/src/main/js/nav/app.js +++ b/server/sonar-web/src/main/js/nav/app.js @@ -40,24 +40,6 @@ define([ } App.addInitializer(function () { - var navHeight = $('.navbar-global').outerHeight() + $('.navbar-context').outerHeight(); - $('.page-wrapper').css('padding-top', navHeight).data('top-offset', navHeight); - }); - - App.addInitializer(function () { - var that = this; - $(window).on('keypress', function (e) { - var tagName = e.target.tagName; - if (tagName !== 'INPUT' && tagName !== 'SELECT' && tagName !== 'TEXTAREA') { - var code = e.keyCode || e.which; - if (code === 63) { - that.navbarView.showShortcutsHelp(); - } - } - }); - }); - - App.addInitializer(function () { var that = this; $(window).on('keypress', function (e) { var tagName = e.target.tagName; diff --git a/server/sonar-web/src/main/js/source-viewer/viewer.js b/server/sonar-web/src/main/js/source-viewer/viewer.js index a8ee471090d..c86bb2e0d98 100644 --- a/server/sonar-web/src/main/js/source-viewer/viewer.js +++ b/server/sonar-web/src/main/js/source-viewer/viewer.js @@ -51,7 +51,9 @@ define([ 'click .source-line-duplications': 'showDuplications', 'click .source-line-duplications-extra': 'showDuplicationPopup', 'click .source-line-with-issues': 'onLineIssuesClick', - 'click .source-line-number[data-line-number]': 'onLineNumberClick' + 'click .source-line-number[data-line-number]': 'onLineNumberClick', + 'mouseenter .source-line-filtered .source-line-filtered-container': 'showFilteredTooltip', + 'mouseleave .source-line-filtered .source-line-filtered-container': 'hideFilteredTooltip' }; }, @@ -82,6 +84,7 @@ define([ if (this.model.has('filterLinesFunc')) { this.filterLines(this.model.get('filterLinesFunc')); } + this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' }); }, onClose: function () { @@ -89,6 +92,11 @@ define([ return view.close(); }); this.issueViews = []; + this.clearTooltips(); + }, + + clearTooltips: function () { + this.$('[data-toggle="tooltip"]').tooltip('destroy'); }, onLoaded: function () { @@ -361,6 +369,7 @@ define([ showDuplications: function (e) { var that = this, lineNumber = $(e.currentTarget).closest('.source-line').data('line-number'); + this.clearTooltips(); this.requestDuplications().done(function () { that.render(); that.$el.addClass('source-duplications-expanded'); @@ -620,12 +629,26 @@ define([ }); }, - filterLinesByDate: function (date) { + filterLinesByDate: function (date, label) { var sinceDate = moment(date).toDate(); + this.sinceLabel = label; this.filterLines(function (line) { var scmDate = moment(line.scmDate).toDate(); return scmDate >= sinceDate; }); + }, + + showFilteredTooltip: function (e) { + $(e.currentTarget).tooltip({ + container: 'body', + placement: 'right', + title: tp('source_viewer.tooltip.new_code', this.sinceLabel), + trigger: 'manual' + }).tooltip('show'); + }, + + hideFilteredTooltip: function (e) { + $(e.currentTarget).tooltip('destroy'); } }); diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/quality-gates-spec.js b/server/sonar-web/src/main/js/tests/e2e/tests/quality-gates-spec.js index f068858dd67..6655b4eb9ac 100644 --- a/server/sonar-web/src/main/js/tests/e2e/tests/quality-gates-spec.js +++ b/server/sonar-web/src/main/js/tests/e2e/tests/quality-gates-spec.js @@ -1,36 +1,42 @@ +/* global casper:false */ + var lib = require('../lib'); lib.initMessages(); lib.changeWorkingDirectory('quality-gates-spec'); -casper.test.begin('Quality Gates', function suite(test) { - casper.start(lib.buildUrl('quality-gates'), function() { - lib.setDefaultViewport(); - - lib.mockRequest('/api/l10n/index', '{}'); - lib.mockRequestFromFile('/api/qualitygates/app', 'app.json'); - lib.mockRequestFromFile('/api/qualitygates/list', 'list.json'); - lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json'); - }); - - casper.waitWhileSelector("div#quality-gates-loader", function() { - - casper.waitForSelector('li.active', function() { - test.assertElementCount('li.active', 1); - test.assertSelectorHasText('ol.navigator-results-list li', 'Default Gate'); - }); - - casper.waitForSelector('div.navigator-header', function() { - test.assertSelectorHasText('div.navigator-header h1', 'Default Gate'); - }); - - casper.waitForSelector('table.quality-gate-conditions tbody tr:nth-child(9)', function() { - test.assertElementCount('table.quality-gate-conditions tbody tr', 9); - }); - }); - - casper.run(function() { - test.done(); - }); +casper.test.begin('Quality Gates', function suite (test) { + casper + .start(lib.buildUrl('quality-gates'), function () { + lib.setDefaultViewport(); + + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/qualitygates/app', 'app.json'); + lib.mockRequestFromFile('/api/qualitygates/list', 'list.json'); + lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json'); + }) + + .then(function () { + casper.waitForSelector('.active', function () { + test.assertElementCount('.active', 1); + test.assertSelectorHasText('.search-navigator-side .active', 'Default Gate'); + }); + }) + + .then(function () { + casper.waitForSelector('.search-navigator-workspace-header', function () { + test.assertSelectorHasText('.search-navigator-workspace-header', 'Default Gate'); + }); + }) + + .then(function () { + casper.waitForSelector('table.quality-gate-conditions tbody tr:nth-child(9)', function () { + test.assertElementCount('table.quality-gate-conditions tbody tr', 9); + }); + }) + + .run(function () { + test.done(); + }); }); diff --git a/server/sonar-web/src/main/js/tests/e2e/views/quality-gates.jade b/server/sonar-web/src/main/js/tests/e2e/views/quality-gates.jade index 1a10f0b5b34..ac1a4871c1b 100644 --- a/server/sonar-web/src/main/js/tests/e2e/views/quality-gates.jade +++ b/server/sonar-web/src/main/js/tests/e2e/views/quality-gates.jade @@ -7,5 +7,4 @@ block header block body #content - #quality-gate-page-loader.navigator-page-loader - i.spinner + .search-navigator#quality-gates diff --git a/server/sonar-web/src/main/less/components/issues.less b/server/sonar-web/src/main/less/components/issues.less index 8177f088984..1d16cffd90a 100644 --- a/server/sonar-web/src/main/less/components/issues.less +++ b/server/sonar-web/src/main/less/components/issues.less @@ -133,7 +133,7 @@ .issue-meta-label { display: inline-block; vertical-align: top; - max-width: 110px; + max-width: 180px; .text-ellipsis; } diff --git a/server/sonar-web/src/main/less/components/navbar.less b/server/sonar-web/src/main/less/components/navbar.less index 2c8da63e581..605f50f3d61 100644 --- a/server/sonar-web/src/main/less/components/navbar.less +++ b/server/sonar-web/src/main/less/components/navbar.less @@ -5,9 +5,8 @@ @navbarGlobalBackground: #262626; @navbarContextBackground: @barBackgroundColor; -@navbarHeight: 30px; @navbarLineHeight: 20px; -@navbarTopPadding: (@navbarHeight - @navbarLineHeight) / 2; +@navbarTopPadding: (@navbarGlobalHeight - @navbarLineHeight) / 2; .navbar, [class^="navbar-"], [class*=" navbar-"] { .box-sizing(border-box); @@ -18,7 +17,7 @@ left: 0; right: 0; .clearfix; - height: @navbarHeight; + height: @navbarGlobalHeight; } .navbar-fade { @@ -184,9 +183,9 @@ .navbar-context { - top: @navbarHeight; + top: @navbarGlobalHeight; z-index: 498; - height: 57px; + height: @navbarContextHeight; background-color: @navbarContextBackground; .nav-tabs { @@ -194,8 +193,8 @@ } .navbar-nav > li > a { - padding-top: 2px; - padding-bottom: 2px; + padding-top: 3px; + padding-bottom: 3px; } } @@ -205,7 +204,7 @@ } .navbar-context-meta { - line-height: @navbarHeight; + line-height: @navbarGlobalHeight; padding: 0 10px; color: @secondFontColor; font-size: @smallFontSize; diff --git a/server/sonar-web/src/main/less/components/navigator/base.less b/server/sonar-web/src/main/less/components/navigator/base.less index 9b448f74e4b..29f7d7f2dba 100644 --- a/server/sonar-web/src/main/less/components/navigator/base.less +++ b/server/sonar-web/src/main/less/components/navigator/base.less @@ -21,7 +21,6 @@ .navigator-filters { position: relative; - margin: @navigatorPadding; border: 1px solid @navigatorBorderLightColor; .box-sizing(border-box); } @@ -72,7 +71,7 @@ .navigator-details { position: relative; - margin: 0 @navigatorPadding @navigatorPadding; + margin: @navigatorPadding 0 0 0; } .navigator-resizer { @@ -95,7 +94,6 @@ .measures-page { .navigator-details { overflow: visible; } - .page { padding: 0 0 0 @navigatorPadding; } } diff --git a/server/sonar-web/src/main/less/components/navigator/filters.less b/server/sonar-web/src/main/less/components/navigator/filters.less index 7c56f015d96..67fbac25279 100644 --- a/server/sonar-web/src/main/less/components/navigator/filters.less +++ b/server/sonar-web/src/main/less/components/navigator/filters.less @@ -312,9 +312,7 @@ } .navigator-filter-favorite { - position: absolute; - top: -@navigatorPadding - @navigatorFiltersHeight; - left: 0; + position: relative; } .navigator-filter-favorite-toggle { diff --git a/server/sonar-web/src/main/less/components/page.less b/server/sonar-web/src/main/less/components/page.less index 207bbc31737..05c57de58a9 100644 --- a/server/sonar-web/src/main/less/components/page.less +++ b/server/sonar-web/src/main/less/components/page.less @@ -4,6 +4,7 @@ .page { + .clearfix; position: relative; padding: 10px; } @@ -17,6 +18,19 @@ .box-sizing(border-box); } +.page-wrapper-global { + padding-top: @navbarGlobalHeight; +} + +.page-wrapper-context { + padding-top: @navbarGlobalHeight + @navbarContextHeight; +} + +.page-simple { + margin: 50px 180px 0; + text-align: left; +} + .page-header { .clearfix; margin-bottom: 10px; diff --git a/server/sonar-web/src/main/less/components/source.less b/server/sonar-web/src/main/less/components/source.less index 361634ac603..bce28e3441a 100644 --- a/server/sonar-web/src/main/less/components/source.less +++ b/server/sonar-web/src/main/less/components/source.less @@ -26,6 +26,7 @@ .source-line-coverage, .source-line-duplications, .source-line-duplications-extra, + .source-line-filtered-container, .source-line-scm { border-color: darken(@barBackgroundColor, 4%); background-color: darken(@barBackgroundColor, 4%); @@ -43,6 +44,7 @@ .source-line-coverage, .source-line-duplications, .source-line-duplications-extra, + .source-line-filtered-container, .source-line-scm { border-color: #fdf190 !important; background-color: #fdf190; @@ -60,9 +62,8 @@ } .source-line-filtered { - .source-line-code { - border-left: 4px solid @lightBlue; - padding-left: 6px; + .source-line-filtered-container { + background-color: @lightBlue !important; } } @@ -157,6 +158,10 @@ } } +.source-line-filtered-container { + background-color: @barBackgroundColor; +} + .source-line-scm { padding: 0 5px; background-color: @barBackgroundColor; diff --git a/server/sonar-web/src/main/less/components/ui.less b/server/sonar-web/src/main/less/components/ui.less index 311a2ca8328..418fe0a7896 100644 --- a/server/sonar-web/src/main/less/components/ui.less +++ b/server/sonar-web/src/main/less/components/ui.less @@ -101,164 +101,6 @@ a.active-link { -/* - * Inputs - */ - -input[type=text], -input[type=password], -input[type=email], -input[type=search], -textarea { - border: 1px solid @darkGrey; - .box-sizing(border-box); - background: #fff; - color: @baseFontColor; - .trans(border-color); - - &:active, - &:focus { - border-color: @highlighted; - box-shadow: none; - outline: none; - } - - &.invalid { border-color: @red; } -} - -input[type=text], -input[type=password], -input[type=email], -input[type=search] { - height: @formControlHeight; - padding: 0 3px; -} - -input[type=search] { - -webkit-appearance: none; -} - -textarea { - padding: 3px; -} - -button, -.button, -input[type=submit], -input[type=button] { - display: inline-block; - vertical-align: baseline; - height: @formControlHeight; - margin: 0 1px; - padding: 0 10px; - - border: 1px solid @darkGrey; - .box-sizing(border-box); - - background: #f4f4f4; - - color: @baseFontColor; - font-weight: bold; - font-size: @baseFontSize; - text-align: center; - text-decoration: none; - - cursor: pointer; - outline: none; - .trans(border-color); - - &:hover, &.active { - border-color: #5281a0; - background: #4b9fd5; - color: #fff; - } - - &:active { - border-color: #2790c0; - background: #78bdea; - color: #fff; - } - - &:focus { - border-color: @highlighted; - } - - &[disabled], - &[disabled]:hover, - &[disabled]:active, - &[disabled]:focus { - color: #bbb; - border-color: #ddd; - background: #ebebeb; - cursor: default; - } -} - -.button { line-height: @formControlHeight; } - -.button-red { - &:hover, &:focus { - border-color: #900; - background: lighten(#900, 10%); - color: #fff; - } - - &:active { - border-color: #900; - background: lighten(#900, 20%); - } -} - -.button-clean, -.button-clean:hover, -.button-clean:focus { - margin: 0; - padding: 0; - border: none; - background: transparent; - color: @baseFontColor; -} - -.button-group { - display: inline-block; - vertical-align: middle; - font-size: 0; - white-space: nowrap; - - & > button, - & > .button { - position: relative; - z-index: 2; - display: inline-block; - vertical-align: middle; - margin: 0; - padding: 2px 8px; - font-size: @smallFontSize; - font-weight: normal; - cursor: pointer; - - &:hover, &:focus, &:active, &.active { - z-index: 3; - } - } - - & > .button { line-height: 16px; } - - & > button + button, - & > button + .button, - & > .button + button, - & > .button + .button { - margin-left: -1px; - } - - & > a:not(.button) { - vertical-align: middle; - margin: 0 8px; - font-size: @smallFontSize; - } -} - - .message-notice { display: block; padding: 5px 8px; @@ -466,6 +308,7 @@ input[type=button] { } .nav-tabs { + padding-top: 1px; border-bottom: 1px solid @barBorderColor; > li { diff --git a/server/sonar-web/src/main/less/init.less b/server/sonar-web/src/main/less/init.less index 86dd9fc5b3a..c2fe9052204 100644 --- a/server/sonar-web/src/main/less/init.less +++ b/server/sonar-web/src/main/less/init.less @@ -3,5 +3,6 @@ @import "init/links"; @import "init/tables"; @import "init/lists"; +@import "init/forms"; @import "init/icons"; @import "init/misc"; diff --git a/server/sonar-web/src/main/less/init/forms.less b/server/sonar-web/src/main/less/init/forms.less new file mode 100644 index 00000000000..9f8be907f17 --- /dev/null +++ b/server/sonar-web/src/main/less/init/forms.less @@ -0,0 +1,164 @@ +@import (reference) "../variables"; +@import (reference) "../mixins"; +@import (reference) "../components/ui"; + +/* + * Inputs + */ + +input[type=text], +input[type=password], +input[type=email], +input[type=search], +textarea { + border: 1px solid @darkGrey; + .box-sizing(border-box); + background: #fff; + color: @baseFontColor; + .trans(border-color); + + &:active, + &:focus { + border-color: @highlighted; + box-shadow: none; + outline: none; + } + + &.invalid { border-color: @red; } +} + +input[type=text], +input[type=password], +input[type=email], +input[type=search] { + height: @formControlHeight; + padding: 0 3px; +} + +input[type=search] { + -webkit-appearance: none; +} + +textarea { + padding: 3px; +} + +button, +.button, +input[type=submit], +input[type=button] { + display: inline-block; + vertical-align: baseline; + height: @formControlHeight; + margin: 0 1px; + padding: 0 10px; + + border: 1px solid @darkGrey; + .box-sizing(border-box); + + background: #f4f4f4; + + color: @baseFontColor; + font-weight: bold; + font-size: @baseFontSize; + text-align: center; + text-decoration: none; + + cursor: pointer; + outline: none; + .trans(border-color); + + &:hover, &.active { + border-color: #5281a0; + background: #4b9fd5; + color: #fff; + } + + &:active { + border-color: #2790c0; + background: #78bdea; + color: #fff; + } + + &:focus { + border-color: @highlighted; + } + + &[disabled], + &[disabled]:hover, + &[disabled]:active, + &[disabled]:focus { + color: #bbb; + border-color: #ddd; + background: #ebebeb; + cursor: default; + } +} + +.button { line-height: @formControlHeight; } + +.button-red { + &:hover, &:focus { + border-color: #900; + background: lighten(#900, 10%); + color: #fff; + } + + &:active { + border-color: #900; + background: lighten(#900, 20%); + } +} + +.button-clean, +.button-clean:hover, +.button-clean:focus { + margin: 0; + padding: 0; + border: none; + background: transparent; + color: @baseFontColor; +} + +.button-group { + display: inline-block; + vertical-align: middle; + font-size: 0; + white-space: nowrap; + + & > button, + & > .button { + position: relative; + z-index: 2; + display: inline-block; + vertical-align: middle; + margin: 0; + padding: 2px 8px; + font-size: @smallFontSize; + font-weight: normal; + cursor: pointer; + + &:hover, &:focus, &:active, &.active { + z-index: 3; + } + } + + & > .button { line-height: 16px; } + + & > button + button, + & > button + .button, + & > .button + button, + & > .button + .button { + margin-left: -1px; + } + + & > a:not(.button) { + vertical-align: middle; + margin: 0 8px; + font-size: @smallFontSize; + } +} + +.input-small { + width: 80px; +} diff --git a/server/sonar-web/src/main/less/init/type.less b/server/sonar-web/src/main/less/init/type.less index 8a531cde54f..b6afe641785 100644 --- a/server/sonar-web/src/main/less/init/type.less +++ b/server/sonar-web/src/main/less/init/type.less @@ -98,3 +98,7 @@ small, .text-justify { text-align: justify; } + +.text-danger { + color: @red; +} diff --git a/server/sonar-web/src/main/less/pages/quality-gates.less b/server/sonar-web/src/main/less/pages/quality-gates.less index 9734d8f5b8e..eea7e0bf0d0 100644 --- a/server/sonar-web/src/main/less/pages/quality-gates.less +++ b/server/sonar-web/src/main/less/pages/quality-gates.less @@ -2,6 +2,14 @@ @import (reference) "../mixins"; @import (reference) "../components/navigator/config"; +.quality-gates-side { + background-color: @barBackgroundColor; +} + +.quality-gates-results { + padding: 5px 0; +} + @qualityGateSidebarWidth: 230px; .quality-gates-navigator { diff --git a/server/sonar-web/src/main/less/sonar-colorizer.less b/server/sonar-web/src/main/less/sonar-colorizer.less index e3b2130c298..5560393ccbc 100644 --- a/server/sonar-web/src/main/less/sonar-colorizer.less +++ b/server/sonar-web/src/main/less/sonar-colorizer.less @@ -1,3 +1,6 @@ +@import (reference) 'variables'; +@import (reference) 'mixins'; + /* for example java annotations */ .code .a { color: #808000; @@ -5,7 +8,7 @@ /* constants */ .code .c { color: #660E80; - font-style: italic; + font-style: normal; font-weight: bold; } /* javadoc */ @@ -25,13 +28,13 @@ } /* keyword */ .code .k { - color: #000080; - font-weight: bold; + color: saturate(@darkBlue, 50%); + font-weight: 600; } /* string */ .code .s { - color: #008000; - font-weight: bold; + color: saturate(@red, 0%); + font-weight: normal; } /* keyword light*/ .code .h { diff --git a/server/sonar-web/src/main/less/variables.less b/server/sonar-web/src/main/less/variables.less index 0857574ca68..1cc10911613 100644 --- a/server/sonar-web/src/main/less/variables.less +++ b/server/sonar-web/src/main/less/variables.less @@ -127,4 +127,6 @@ * Page */ +@navbarGlobalHeight: 30px; +@navbarContextHeight: 60px; @pageFooterHeight: 60px; diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/events_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/events_controller.rb index ebf29970d09..e6d4696c397 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/events_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/events_controller.rb @@ -51,7 +51,7 @@ class Api::EventsController < Api::ApiController end if from conditions<<'event_date>=:from' - values[:from]=from + values[:from]=from.to_i*1000 end to=nil @@ -62,7 +62,7 @@ class Api::EventsController < Api::ApiController end if to conditions<<'event_date<=:to' - values[:to]=to + values[:to]=to.to_i*1000 end events=Event.find(:all, :conditions => [conditions.join(' AND '), values], :order => 'event_date DESC') diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb index 23d172dde89..3b14a29da01 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb @@ -107,6 +107,11 @@ class IssueController < ApplicationController end end + def show + # the redirect is needed for the backward compatibility with eclipse plugin + redirect_to :action => 'search', :anchor => 'issues=' + params[:id] + end + # # diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb index 3b00a8a16b0..3ac6eba4b8d 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb @@ -371,9 +371,13 @@ class ProfilesController < ApplicationController # GET /profiles/compare?id1=<profile1 id>&id2=<profile2 id> def compare @profiles = Profile.all(:order => 'language asc, name') - if params[:id1].present? && params[:id2].present? - @profile1 = Profile.find(params[:id1]) - @profile2 = Profile.find(params[:id2]) + id1 = params[:id1] + id2 = params[:id2] + if id1.present? && id2.present? && id1.respond_to?(:to_i) && id2.respond_to?(:to_i) + @id1 = params[:id1].to_i + @id2 = params[:id2].to_i + @profile1 = Profile.find(id1) + @profile2 = Profile.find(id2) arules1 = ActiveRule.all(:include => [{:active_rule_parameters => :rules_parameter}, :rule], :conditions => ['active_rules.profile_id=?', @profile1.id]) diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb index 4831457ad1a..e384ae00248 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb @@ -25,6 +25,7 @@ class ResourceController < ApplicationController helper :dashboard helper UsersHelper + # DO NOT REMOVE - used by eclipse plugin def index require_parameters 'id' diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/models/event.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/models/event.rb index 97d5c277e7a..d5349ebaad3 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/models/event.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/models/event.rb @@ -27,6 +27,27 @@ class Event < ActiveRecord::Base belongs_to :snapshot before_save :populate_snapshot + + def created_at + long_to_date(:created_at) + end + + def created_at=(date) + write_attribute(:created_at, date.to_i*1000) + end + + def event_date + long_to_date(:event_date) + end + + def event_date=(date) + write_attribute(:event_date, date.to_i*1000) + end + + def long_to_date(attribute) + date_in_long = read_attribute(attribute) + Time.at(date_in_long/1000) if date_in_long + end def fullname if category @@ -59,10 +80,8 @@ class Event < ActiveRecord::Base return false end - # - # TODO: Remove this code when everything has been checked on the Event handling, both on the UI and the WS API - # def populate_snapshot + self.created_at=DateTime.now unless self.created_at self.snapshot=Snapshot.snapshot_by_date(resource_id, event_date) unless self.snapshot end end diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/all_projects/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/all_projects/index.html.erb index a1db3f3e830..31f11fd94fe 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/all_projects/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/all_projects/index.html.erb @@ -1,21 +1,23 @@ <% if @filter.rows %> + <div class="page"> + <header class="page-header"> + <h1 class="page-title"><%= message('qualifiers.all.' + @qualifier) -%></h1> + </header> - <h1 class="marginbottom10"><%= message('qualifiers.all.' + @qualifier) -%></h1> + <div id="all-projects"> - <div id="all-projects"> + <% if @filter.security_exclusions %> + <p class="notes"><%= message('all-projects.results_not_display_due_to_security') -%></p> + <% end %> - <% if @filter.security_exclusions %> - <p class="notes"><%= message('all-projects.results_not_display_due_to_security') -%></p> - <% end %> + <% + display_favourites = logged_in? + colspan = 5 + colspan += 1 if display_favourites + %> - <% - display_favourites = logged_in? - colspan = 5 - colspan += 1 if display_favourites - %> - - <table class="data" id="all-projects-table"> - <thead> + <table class="data" id="all-projects-table"> + <thead> <tr> <% if display_favourites %> <th class="thin" style></th> @@ -29,16 +31,16 @@ <th></th> <th></th> </tr> - </thead> - - <tbody> + </thead> + + <tbody> <% @filter.rows.each do |row| %> <tr class="thin <%= cycle 'even', 'odd' -%>"> <% if display_favourites %> <td class="thin"><%= link_to_favourite(row.snapshot.resource) -%></td> <% end %> <td class="nowrap"> - <%= qualifier_icon(row.snapshot)-%> <%= link_to(h(row.snapshot.resource.name(true)), {:controller => 'dashboard', :id => row.snapshot.resource_id}, :title => h(row.snapshot.resource.key)) -%> + <%= qualifier_icon(row.snapshot) -%> <%= link_to(h(row.snapshot.resource.name(true)), {:controller => 'dashboard', :id => row.snapshot.resource_id}, :title => h(row.snapshot.resource.key)) -%> </td> <td class="sep"></td> <td> @@ -46,32 +48,34 @@ </td> <td class="sep"></td> <td class="nowrap right"> - <% + <% if row.links row.links.select { |link| link.href.start_with?('http') }.each do |link| %> - <a target="_blank" href="<%= link.href -%>" class="icon-<%= link.link_type -%>"></a> - <% + <a target="_blank" href="<%= link.href -%>" class="icon-<%= link.link_type -%>"></a> + <% end - end - %> + end + %> </td> </tr> <% end %> - + <% if @filter.rows.empty? %> <tr class="even"> <td colspan="<%= colspan -%>"><%= message 'no_data' -%></td> </tr> <% end %> - </tbody> + </tbody> + + <%= table_pagination(@filter.pagination, :colspan => colspan) { |label, page_id| + link_to(label, :action => 'index', :qualifier => h(@qualifier), :asc => h(@filter.criteria[:asc]), :page => page_id) + } + -%> + + </table> + </div> - <%= table_pagination(@filter.pagination, :colspan => colspan) { |label, page_id| - link_to(label, :action => 'index', :qualifier => h(@qualifier), :asc => h(@filter.criteria[:asc]), :page => page_id) - } - -%> - - </table> </div> - + <% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/configure.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/configure.html.erb index 30c43cc9aa0..3bb4a42d099 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/configure.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/configure.html.erb @@ -1,4 +1,4 @@ -<div id="dashboard"> +<div class="page" id="dashboard"> <%= render :partial => 'header', :locals => {:back => true} %> <div id="configure"> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/no_dashboard.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/no_dashboard.html.erb index cc4f431d7bd..ecef10c64d7 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/no_dashboard.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/no_dashboard.html.erb @@ -12,5 +12,5 @@ key: '<%= @resource.key -%>' }; document.querySelector('.navbar-context').remove(); + jQuery('.page-wrapper-context').addClass('page-wrapper-global').removeClass('page-wrapper-context'); </script> - diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/measures.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/measures.html.erb index eaf925d5c44..24864f5bcf5 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/measures.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/measures.html.erb @@ -125,7 +125,8 @@ metric: <% if @metric %>'<%= @metric.key -%>'<% else %>null<% end %>, rule: null, severity: null, - period: <% if @period %>'<%= @snapshot.period_datetime(@period) -%>'<% else %>null<% end %> + period: <% if @period %>'<%= @snapshot.period_datetime(@period) -%>'<% else %>null<% end %>, + periodName: <% if @period %>'<%= period_label(@snapshot, @period) -%>'<% else %>null<% end %>, }; </script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb index d426dd667d9..be930090850 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb @@ -4,12 +4,13 @@ selected_section = Navigation::SECTION_HOME end @project=@resource unless @project || selected_section==Navigation::SECTION_HOME + has_context_nav = selected_section==Navigation::SECTION_RESOURCE || selected_section==Navigation::SECTION_CONFIGURATION period_param = "period=#{u(params[:period])}" if params[:period] %> -<div class="page-wrapper" id="container"> +<div class="page-wrapper <% if has_context_nav %>page-wrapper-context<% else %>page-wrapper-global<% end %>" id="container"> <nav class="navbar navbar-global page-container" id="global-navigation"></nav> - <% if selected_section==Navigation::SECTION_RESOURCE || selected_section==Navigation::SECTION_CONFIGURATION %> + <% if has_context_nav %> <nav class="navbar navbar-context page-container" id="context-navigation"></nav> <% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/nonav.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/nonav.html.erb index 7637ea77cf9..2c76816b05c 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/nonav.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/nonav.html.erb @@ -1,7 +1,7 @@ <%= render :partial => 'layouts/head' %> <div> <div id="bd"> - <div id="nonav"> + <div id="nonav" class="page-simple"> <%= yield %> </div> </div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/maintenance/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/maintenance/index.html.erb index 8571ebb1edd..87c55d92872 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/maintenance/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/maintenance/index.html.erb @@ -4,14 +4,9 @@ border: 2px solid #4B9FD5; background-color: #CAE3F2; } -#maintenancelogo { - float: right; - padding-left: 20px; -} </style> <div id="maintenance"> -<div id="maintenancelogo"><a href="http://www.sonarqube.org"><%= image_tag('logo.png', :class => 'png') -%></a></div> <h1>SonarQube is under maintenance. <a href="<%= ApplicationController.root_context -%>/">Please check back later.</a></h1> <p>Whilst waiting, you might want to check <a href="http://sonar-plugins.codehaus.org">new plugins</a> to extend the current functionality. </p><p>If you are an administrator and have no idea why this message is showing, you should read the <a href="http://redirect.sonarsource.com/doc/upgrading.html">upgrade guide</a>.</p> </div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_search_body.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_search_body.html.erb index d1370ecc8b8..ac365ce8836 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_search_body.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_search_body.html.erb @@ -6,7 +6,5 @@ <p class="notes"><%= message('results_not_display_due_to_security') -%></p> <% end %> - <div class="page"> - <%= render :partial => 'measures/display', :locals => {:filter => @filter, :edit_mode => edit_mode, :widget_id => nil} -%> - </div> + <%= render :partial => 'measures/display', :locals => {:filter => @filter, :edit_mode => edit_mode, :widget_id => nil} -%> <% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_search_header.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_search_header.html.erb index de527f0ed81..fa098e97750 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_search_header.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_search_header.html.erb @@ -46,20 +46,20 @@ <% end %> <% end %> </div> - </div> - <% unless edit_mode %> - <% if @filter.display %> - <div class="navigator-header-actions button-group"> - <button id="change-display" onclick="window.location='<%= url_for @filter.criteria.merge({:action => 'search', :edit => true, :id => @filter.id}) -%>';"><%= message("measure_filter.#{@filter.display.key}.change") -%></button> - </div> + <% unless edit_mode %> + <% if @filter.display %> + <div class="button-group"> + <button id="change-display" onclick="window.location='<%= url_for @filter.criteria.merge({:action => 'search', :edit => true, :id => @filter.id}) -%>';"><%= message("measure_filter.#{@filter.display.key}.change") -%></button> + </div> + <% end %> <% end %> - <% end %> + </div> <% if @filter.description.present? %> - <div id="filter-description" class="navigator-header-description"><%= h @filter.description -%></div> + <div id="filter-description" class="page-description"><%= h @filter.description -%></div> <% end %> <% else %> - <h1 class="navigator-header-title"><%= message('layout.measures') -%></h1> + <h1 class="page-title"><%= message('layout.measures') -%></h1> <% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/search.html.erb index bf9ad6c0345..bbdaaeb8b22 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/search.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/search.html.erb @@ -3,10 +3,10 @@ <% end %> -<div class="navigator"> - <div class="navigator-header"> +<div class="page"> + <header class="page-header"> <%= render :partial => 'search_header' -%> - </div> + </header> <div class="navigator-filters"></div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/profiles/compare.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/profiles/compare.html.erb index 5f0578dc5cc..54051821fc1 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/profiles/compare.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/profiles/compare.html.erb @@ -9,12 +9,12 @@ <form method="GET" class="marginbottom10"> <select name="id1" class="small"> <option value=""></option> - <%= options_for_profiles(@profiles, params[:id1]) %> + <%= options_for_profiles(@profiles, @id1) %> </select> <select name="id2" class="small"> <option value=""></option> - <%= options_for_profiles(@profiles, params[:id2]) %> + <%= options_for_profiles(@profiles, @id2) %> </select> <input type="submit" value="<%= message('compare') -%>" class="small" id="submit-compare"/> </form> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb index 4a1ee1605e2..181ab0efae2 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb @@ -2,6 +2,4 @@ <script>require(['quality-gate/app']);</script> <% end %> -<div id="quality-gate-page-loader" class="navigator-page-loader"> - <i class="spinner"></i> -</div> +<div class="search-navigator" id="quality-gates"></div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/_edit_groups.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/_edit_groups.html.erb index 186d8b2da26..a6ce4faf816 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/_edit_groups.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/_edit_groups.html.erb @@ -1,5 +1,5 @@ <div class="modal-head"> - <h2><%= @project ? "Edit Permission #{message("projects_role.#{@role}")} For: " + h(@project.name) : "Edit Global Permission: #{message("global_permissions.#{@role}")}" -%></h2> + <h2><%= @project ? "Edit Permission #{message("projects_role.#{h @role}")} For: " + h(@project.name) : "Edit Global Permission: #{message("global_permissions.#{h @role}")}" -%></h2> </div> <div class="modal-body"> @@ -21,12 +21,12 @@ } return label; }, - searchUrl: baseUrl + '/permissions/search_groups?permission=<%= @role -%><%= @project ? "&component=" + @project.key : "" -%>', + searchUrl: baseUrl + '/permissions/search_groups?permission=<%= u @role -%><%= @project ? "&component=" + u(@project.key) : "" -%>', selectUrl: baseUrl + '/api/permissions/add', deselectUrl: baseUrl + '/api/permissions/remove', extra: { - permission: '<%= @role -%>' - <%= @project ? ", component: '" + @project.key + "'" : "" %> + permission: '<%= escape_javascript @role -%>' + <%= @project ? ", component: '" + escape_javascript(@project.key) + "'" : "" %> }, selectParameter: 'group', selectParameterValue: 'name', diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/_edit_users.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/_edit_users.html.erb index 3e2bad239c8..1c08cbf5269 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/_edit_users.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/_edit_users.html.erb @@ -1,5 +1,5 @@ <div class="modal-head"> - <h2><%= @project ? "Edit Permission #{message("projects_role.#{@role}")} For: " + h(@project.name) : "Edit Global Permission: #{message("global_permissions.#{@role}")}" -%></h2> + <h2><%= @project ? "Edit Permission #{message("projects_role.#{h @role }")} For: " + h(@project.name) : "Edit Global Permission: #{message("global_permissions.#{h @role}")}" -%></h2> </div> <div class="modal-body"> @@ -15,12 +15,12 @@ el: '#select-users-permissions', width: '100%', format: function (item) { return item.name + ' <div class="subtitle">' + item.login + '</div>'; }, - searchUrl: baseUrl + '/permissions/search_users?permission=<%= @role -%><%= @project ? "&component=" + @project.key : "" -%>', + searchUrl: baseUrl + '/permissions/search_users?permission=<%= u @role -%><%= @project ? "&component=" + u(@project.key) : "" -%>', selectUrl: baseUrl + '/api/permissions/add', deselectUrl: baseUrl + '/api/permissions/remove', extra: { - permission: '<%= @role -%>' - <%= @project ? ", component: '" + @project.key + "'" : "" %> + permission: '<%= escape_javascript @role -%>' + <%= @project ? ", component: '" + escape_javascript(@project.key) + "'" : "" %> }, selectParameter: 'user', selectParameterValue: 'login', diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/791_add_events_long_dates.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/791_add_events_long_dates.rb new file mode 100644 index 00000000000..ae4f097c7f0 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/791_add_events_long_dates.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2014 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube 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. +# +# SonarQube 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 5.1 +# +class AddEventsLongDates < ActiveRecord::Migration + def self.up + add_column 'events', :event_date_ms, :big_integer, :null => true + add_column 'events', :created_at_ms, :big_integer, :null => true + end +end diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/792_feed_events_long_dates.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/792_feed_events_long_dates.rb new file mode 100644 index 00000000000..b058d46e559 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/792_feed_events_long_dates.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2014 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube 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. +# +# SonarQube 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 5.1 +# +class FeedEventsLongDates < ActiveRecord::Migration + def self.up + execute_java_migration('org.sonar.server.db.migrations.v51.FeedEventsLongDates') + end +end + diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/793_rename_events_long_dates.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/793_rename_events_long_dates.rb new file mode 100644 index 00000000000..a46e2a5c488 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/793_rename_events_long_dates.rb @@ -0,0 +1,34 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2014 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube 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. +# +# SonarQube 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 5.1 +# +class RenameEventsLongDates < ActiveRecord::Migration + def self.up + remove_column 'events', 'created_at' + remove_column 'events', 'event_date' + rename_column 'events', 'created_at_ms', 'created_at' + rename_column 'events', 'event_date_ms', 'event_date' + change_column 'events', 'created_at', :big_integer, :null => false + change_column 'events', 'event_date', :big_integer, :null => false + end +end + diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/794_add_characteristic_usability_and_sub_characteristics_compliance.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/794_add_characteristic_usability_and_sub_characteristics_compliance.rb new file mode 100644 index 00000000000..4c65c3c3864 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/794_add_characteristic_usability_and_sub_characteristics_compliance.rb @@ -0,0 +1,31 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2014 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube 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. +# +# SonarQube 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 5.1 +# SONAR-6187 +# +class AddCharacteristicUsabilityAndSubCharacteristicsCompliance < ActiveRecord::Migration + + def self.up + execute_java_migration 'org.sonar.server.db.migrations.v51.AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration' + end + +end diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java index 58e6e6e747a..daa3c1d0eea 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java @@ -19,6 +19,8 @@ */ package org.sonar.batch.bootstrap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; @@ -27,6 +29,8 @@ import org.sonar.batch.protocol.input.GlobalRepositories; public class GlobalSettings extends Settings { + private static final Logger LOG = LoggerFactory.getLogger(GlobalSettings.class); + private final BootstrapProperties bootstrapProps; private final GlobalRepositories globalReferentials; private final DefaultAnalysisMode mode; @@ -45,6 +49,7 @@ public class GlobalSettings extends Settings { private void init() { addProperties(globalReferentials.globalSettings()); addProperties(bootstrapProps.properties()); + LOG.info("Server id: " + getString(CoreProperties.SERVER_ID)); } @Override diff --git a/sonar-batch/src/main/java/org/sonar/batch/cpd/CpdComponents.java b/sonar-batch/src/main/java/org/sonar/batch/cpd/CpdComponents.java index 6032e60b9b6..8ab889c30d5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/cpd/CpdComponents.java +++ b/sonar-batch/src/main/java/org/sonar/batch/cpd/CpdComponents.java @@ -20,10 +20,6 @@ package org.sonar.batch.cpd; import com.google.common.collect.ImmutableList; -import org.sonar.api.CoreProperties; -import org.sonar.api.PropertyType; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.resources.Qualifiers; import org.sonar.batch.cpd.decorators.DuplicationDensityDecorator; import org.sonar.batch.cpd.decorators.SumDuplicationsDecorator; import org.sonar.batch.cpd.index.IndexFactory; @@ -34,38 +30,6 @@ public final class CpdComponents { public static List all() { return ImmutableList.of( - PropertyDefinition.builder(CoreProperties.CPD_CROSS_PROJECT) - .defaultValue(CoreProperties.CPD_CROSS_RPOJECT_DEFAULT_VALUE + "") - .name("Cross project duplication detection") - .description("By default, SonarQube detects duplications at sub-project level. This means that a block " - + "duplicated on two sub-projects of the same project won't be reported. Setting this parameter to \"true\" " - + "allows to detect duplicates across sub-projects and more generally across projects. Note that activating " - + "this property will slightly increase each SonarQube analysis time.") - .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) - .category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_DUPLICATIONS) - .type(PropertyType.BOOLEAN) - .build(), - PropertyDefinition.builder(CoreProperties.CPD_SKIP_PROPERTY) - .defaultValue("false") - .name("Skip") - .description("Disable detection of duplications") - .hidden() - .category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_DUPLICATIONS) - .type(PropertyType.BOOLEAN) - .build(), - PropertyDefinition.builder(CoreProperties.CPD_EXCLUSIONS) - .defaultValue("") - .name("Duplication Exclusions") - .description("Patterns used to exclude some source files from the duplication detection mechanism. " + - "See below to know how to use wildcards to specify this property.") - .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_DUPLICATIONS_EXCLUSIONS) - .multiValues(true) - .build(), - CpdSensor.class, CpdMappings.class, SumDuplicationsDecorator.class, diff --git a/sonar-batch/src/main/java/org/sonar/batch/cpd/JavaCpdEngine.java b/sonar-batch/src/main/java/org/sonar/batch/cpd/JavaCpdEngine.java index 2205227cd31..d753fc45e1f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/cpd/JavaCpdEngine.java +++ b/sonar-batch/src/main/java/org/sonar/batch/cpd/JavaCpdEngine.java @@ -83,6 +83,9 @@ public class JavaCpdEngine extends CpdEngine { */ private static final int TIMEOUT = 5 * 60; + private static final int MAX_CLONE_GROUP_PER_FILE = 100; + private static final int MAX_CLONE_PART_PER_GROUP = 100; + private final IndexFactory indexFactory; private final FileSystem fs; private final Settings settings; @@ -230,12 +233,25 @@ public class JavaCpdEngine extends CpdEngine { .setFromCore() .save(); + int cloneGroupCount = 0; for (CloneGroup duplication : duplications) { + cloneGroupCount++; + if (cloneGroupCount > MAX_CLONE_GROUP_PER_FILE) { + LOG.warn("Too many duplication groups on file " + inputFile.relativePath() + ". Keep only the first " + MAX_CLONE_GROUP_PER_FILE + " groups."); + break; + } NewDuplication builder = context.newDuplication(); ClonePart originPart = duplication.getOriginPart(); builder.originBlock(inputFile, originPart.getStartLine(), originPart.getEndLine()); + int clonePartCount = 0; for (ClonePart part : duplication.getCloneParts()) { if (!part.equals(originPart)) { + clonePartCount++; + if (clonePartCount > MAX_CLONE_PART_PER_GROUP) { + LOG.warn("Too many duplication references on file " + inputFile.relativePath() + " for block at line " + originPart.getStartLine() + ". Keep only the first " + + MAX_CLONE_PART_PER_GROUP + " references."); + break; + } ((DefaultDuplication) builder).isDuplicatedBy(part.getResourceId(), part.getStartLine(), part.getEndLine()); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java index 2e3a6348466..666ec9b633f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java @@ -82,7 +82,7 @@ public class SqaleRatingDecorator implements Decorator { @Override public void decorate(Resource resource, DecoratorContext context) { - if (ResourceUtils.isPersistable(resource) && !ResourceUtils.isUnitTestClass(resource)) { + if (ResourceUtils.isPersistable(resource) && !ResourceUtils.isUnitTestFile(resource)) { Long developmentCost = getDevelopmentCost(context); context.saveMeasure(new Measure(CoreMetrics.DEVELOPMENT_COST, Long.toString(developmentCost))); diff --git a/sonar-batch/src/main/java/org/sonar/batch/dependency/DefaultDependencyValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/dependency/DefaultDependencyValueCoder.java index 8f01f2c233b..6dd1202dc42 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/dependency/DefaultDependencyValueCoder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/dependency/DefaultDependencyValueCoder.java @@ -22,47 +22,26 @@ package org.sonar.batch.dependency; import com.persistit.Value; import com.persistit.encoding.CoderContext; import com.persistit.encoding.ValueCoder; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.sensor.dependency.internal.DefaultDependency; -import org.sonar.batch.scan.filesystem.InputPathCache; class DefaultDependencyValueCoder implements ValueCoder { - private InputPathCache inputPathCache; - - public DefaultDependencyValueCoder(InputPathCache inputPathCache) { - this.inputPathCache = inputPathCache; - } - @Override public void put(Value value, Object object, CoderContext context) { DefaultDependency dep = (DefaultDependency) object; - value.putUTF(((DefaultInputFile) dep.from()).moduleKey()); - value.putUTF(((DefaultInputFile) dep.from()).relativePath()); - value.putUTF(((DefaultInputFile) dep.to()).moduleKey()); - value.putUTF(((DefaultInputFile) dep.to()).relativePath()); + value.putUTF(dep.fromKey()); + value.putUTF(dep.toKey()); value.put(dep.weight()); } @Override public Object get(Value value, Class clazz, CoderContext context) { - String fromModuleKey = value.getString(); - String fromRelativePath = value.getString(); - InputFile from = inputPathCache.getFile(fromModuleKey, fromRelativePath); - if (from == null) { - throw new IllegalStateException("Unable to load InputFile " + fromModuleKey + ":" + fromRelativePath); - } - String toModuleKey = value.getString(); - String toRelativePath = value.getString(); - InputFile to = inputPathCache.getFile(toModuleKey, toRelativePath); - if (to == null) { - throw new IllegalStateException("Unable to load InputFile " + toModuleKey + ":" + toRelativePath); - } + String fromKey = value.getString(); + String toKey = value.getString(); int weight = value.getInt(); return new DefaultDependency() - .from(from) - .to(to) - .weight(weight); + .setFromKey(fromKey) + .setToKey(toKey) + .setWeight(weight); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/dependency/DependencyCache.java b/sonar-batch/src/main/java/org/sonar/batch/dependency/DependencyCache.java index 9e3709a5532..50ce7577c7b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/dependency/DependencyCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/dependency/DependencyCache.java @@ -21,14 +21,10 @@ package org.sonar.batch.dependency; import com.google.common.base.Preconditions; import org.sonar.api.BatchComponent; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.fs.internal.DefaultInputFile; -import org.sonar.api.batch.sensor.dependency.Dependency; import org.sonar.api.batch.sensor.dependency.internal.DefaultDependency; import org.sonar.batch.index.Cache; import org.sonar.batch.index.Cache.Entry; import org.sonar.batch.index.Caches; -import org.sonar.batch.scan.filesystem.InputPathCache; import javax.annotation.CheckForNull; @@ -38,29 +34,29 @@ import javax.annotation.CheckForNull; */ public class DependencyCache implements BatchComponent { - private final Cache<Dependency> cache; + private final Cache<DefaultDependency> cache; - public DependencyCache(Caches caches, InputPathCache inputPathCache) { - caches.registerValueCoder(DefaultDependency.class, new DefaultDependencyValueCoder(inputPathCache)); + public DependencyCache(Caches caches) { + caches.registerValueCoder(DefaultDependency.class, new DefaultDependencyValueCoder()); cache = caches.createCache("dependencies"); } - public Iterable<Entry<Dependency>> entries() { + public Iterable<Entry<DefaultDependency>> entries() { return cache.entries(); } @CheckForNull - public Dependency get(String moduleKey, InputFile from, InputFile to) { + public DefaultDependency get(String moduleKey, String fromKey, String toKey) { Preconditions.checkNotNull(moduleKey); - Preconditions.checkNotNull(from); - Preconditions.checkNotNull(to); - return cache.get(moduleKey, ((DefaultInputFile) from).key(), ((DefaultInputFile) to).key()); + Preconditions.checkNotNull(fromKey); + Preconditions.checkNotNull(toKey); + return cache.get(moduleKey, fromKey, toKey); } - public DependencyCache put(String moduleKey, Dependency dependency) { + public DependencyCache put(String moduleKey, DefaultDependency dependency) { Preconditions.checkNotNull(moduleKey); Preconditions.checkNotNull(dependency); - cache.put(moduleKey, ((DefaultInputFile) dependency.from()).key(), ((DefaultInputFile) dependency.to()).key(), dependency); + cache.put(moduleKey, dependency.fromKey(), dependency.toKey(), dependency); return this; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java index 2b309da1100..a8d96036863 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java @@ -37,19 +37,15 @@ import org.sonar.api.design.Dependency; import org.sonar.api.measures.Measure; import org.sonar.api.measures.MeasuresFilter; import org.sonar.api.measures.Metric; -import org.sonar.api.resources.Directory; -import org.sonar.api.resources.File; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.ProjectLink; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Resource; +import org.sonar.api.resources.*; import org.sonar.api.rules.Violation; import org.sonar.api.utils.SonarException; -import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.sensor.DefaultSensorContext; import org.sonar.batch.sensor.coverage.CoverageExclusions; +import javax.annotation.Nullable; + import java.io.Serializable; import java.util.Collection; import java.util.Date; @@ -66,8 +62,8 @@ public class DeprecatedSensorContext extends DefaultSensorContext implements Sen public DeprecatedSensorContext(SonarIndex index, Project project, Settings settings, FileSystem fs, ActiveRules activeRules, AnalysisMode analysisMode, ComponentDataCache componentDataCache, CoverageExclusions coverageFilter, - DuplicationCache duplicationCache, SensorStorage sensorStorage) { - super(settings, fs, activeRules, analysisMode, componentDataCache, duplicationCache, sensorStorage); + SensorStorage sensorStorage) { + super(settings, fs, activeRules, analysisMode, componentDataCache, sensorStorage); this.index = index; this.project = project; this.coverageFilter = coverageFilter; @@ -255,7 +251,7 @@ public class DeprecatedSensorContext extends DefaultSensorContext implements Sen } @Override - public Event createEvent(Resource resource, String name, String description, String category, Date date) { + public Event createEvent(Resource resource, String name, String description, String category, @Nullable Date date) { return index.addEvent(resource, name, description, category, date); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/DsmDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/design/DsmDecorator.java index 3569ba9a913..0fdae1a037b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/design/DsmDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/design/DsmDecorator.java @@ -75,9 +75,7 @@ public abstract class DsmDecorator implements Decorator { } Dsm<Resource> dsm = getDsm(children, feedbackEdges); // Optimization, don't save DSM if there is no dependency at all - if (dsm.hasAtLeastOneDependency()) { - saveDsm(context, dsm); - } + saveDsm(context, dsm); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/BatchResource.java b/sonar-batch/src/main/java/org/sonar/batch/index/BatchResource.java index 10d4b9d543e..a07eded789c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/BatchResource.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/BatchResource.java @@ -46,6 +46,10 @@ public class BatchResource { } } + public String key() { + return r.getEffectiveKey(); + } + public int batchId() { return batchId; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java b/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java index c98b88773e6..02efaab0f00 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java @@ -300,15 +300,7 @@ public class Cache<V> { * Lazy-loading values for given keys */ public Iterable<V> values(Object firstKey, Object secondKey) { - try { - exchange.clear(); - exchange.append(firstKey).append(secondKey).append(Key.BEFORE); - Exchange iteratorExchange = new Exchange(exchange); - KeyFilter filter = new KeyFilter().append(KeyFilter.simpleTerm(firstKey)).append(KeyFilter.simpleTerm(secondKey)); - return new ValueIterable<V>(iteratorExchange, filter); - } catch (Exception e) { - throw failToGetValues(e); - } + return new ValueIterable<V>(exchange, firstKey, secondKey); } private IllegalStateException failToGetValues(Exception e) { @@ -319,41 +311,22 @@ public class Cache<V> { * Lazy-loading values for a given key */ public Iterable<V> values(Object firstKey) { - try { - exchange.clear(); - exchange.append(firstKey).append(Key.BEFORE); - Exchange iteratorExchange = new Exchange(exchange); - KeyFilter filter = new KeyFilter().append(KeyFilter.simpleTerm(firstKey)); - return new ValueIterable<V>(iteratorExchange, filter); - } catch (Exception e) { - throw failToGetValues(e); - } + return new ValueIterable<V>(exchange, firstKey); } /** * Lazy-loading values */ public Iterable<V> values() { - try { - exchange.clear().append(Key.BEFORE); - Exchange iteratorExchange = new Exchange(exchange); - KeyFilter filter = new KeyFilter().append(KeyFilter.ALL); - return new ValueIterable<V>(iteratorExchange, filter); - } catch (Exception e) { - throw failToGetValues(e); - } + return new ValueIterable<V>(exchange); } public Iterable<Entry<V>> entries() { - exchange.clear().to(Key.BEFORE); - KeyFilter filter = new KeyFilter().append(KeyFilter.ALL); - return new EntryIterable<V>(new Exchange(exchange), filter); + return new EntryIterable<V>(exchange); } public Iterable<Entry<V>> entries(Object firstKey) { - exchange.clear().append(firstKey).append(Key.BEFORE); - KeyFilter filter = new KeyFilter().append(KeyFilter.simpleTerm(firstKey)); - return new EntryIterable<V>(new Exchange(exchange), filter); + return new EntryIterable<V>(exchange, firstKey); } private void resetKey(Object key) { @@ -383,15 +356,25 @@ public class Cache<V> { // private static class ValueIterable<T> implements Iterable<T> { - private final Iterator<T> iterator; + private final Exchange originExchange; + private final Object[] keys; - private ValueIterable(Exchange exchange, KeyFilter keyFilter) { - this.iterator = new ValueIterator<T>(exchange, keyFilter); + private ValueIterable(Exchange originExchange, Object... keys) { + this.originExchange = originExchange; + this.keys = keys; } @Override public Iterator<T> iterator() { - return iterator; + originExchange.clear(); + KeyFilter filter = new KeyFilter(); + for (Object key : keys) { + originExchange.append(key); + filter = filter.append(KeyFilter.simpleTerm(key)); + } + originExchange.append(Key.BEFORE); + Exchange iteratorExchange = new Exchange(originExchange); + return new ValueIterator<T>(iteratorExchange, filter); } } @@ -434,15 +417,25 @@ public class Cache<V> { } private static class EntryIterable<T> implements Iterable<Entry<T>> { - private final EntryIterator<T> it; + private final Exchange originExchange; + private final Object[] keys; - private EntryIterable(Exchange exchange, KeyFilter keyFilter) { - it = new EntryIterator<T>(exchange, keyFilter); + private EntryIterable(Exchange originExchange, Object... keys) { + this.originExchange = originExchange; + this.keys = keys; } @Override public Iterator<Entry<T>> iterator() { - return it; + originExchange.clear(); + KeyFilter filter = new KeyFilter(); + for (Object key : keys) { + originExchange.append(key); + filter = filter.append(KeyFilter.simpleTerm(key)); + } + originExchange.append(Key.BEFORE); + Exchange iteratorExchange = new Exchange(originExchange); + return new EntryIterator<T>(iteratorExchange, filter); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java index 4cc275dc4aa..0e2c128b185 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java @@ -38,14 +38,7 @@ import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.measures.MeasuresFilter; import org.sonar.api.measures.MeasuresFilters; -import org.sonar.api.resources.Directory; -import org.sonar.api.resources.File; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.ProjectLink; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Resource; -import org.sonar.api.resources.ResourceUtils; -import org.sonar.api.resources.Scopes; +import org.sonar.api.resources.*; import org.sonar.api.rules.Rule; import org.sonar.api.rules.Violation; import org.sonar.api.scan.filesystem.PathResolver; @@ -59,16 +52,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; public class DefaultIndex extends SonarIndex { @@ -103,7 +87,11 @@ public class DefaultIndex extends SonarIndex { private final ResourceCache resourceCache; private final MetricFinder metricFinder; - + private final MeasureCache measureCache; + private final ResourceKeyMigration migration; + private final DependencyPersister dependencyPersister; + private final LinkPersister linkPersister; + private final EventPersister eventPersister; // caches private Project currentProject; private Map<Resource, Bucket> buckets = Maps.newLinkedHashMap(); @@ -112,11 +100,6 @@ public class DefaultIndex extends SonarIndex { private Map<Resource, Map<Resource, Dependency>> incomingDependenciesByResource = Maps.newLinkedHashMap(); private ProjectTree projectTree; private ModuleIssues moduleIssues; - private final MeasureCache measureCache; - private final ResourceKeyMigration migration; - private final DependencyPersister dependencyPersister; - private final LinkPersister linkPersister; - private final EventPersister eventPersister; public DefaultIndex(ResourceCache resourceCache, DependencyPersister dependencyPersister, LinkPersister linkPersister, EventPersister eventPersister, ProjectTree projectTree, MetricFinder metricFinder, @@ -131,9 +114,9 @@ public class DefaultIndex extends SonarIndex { this.measureCache = measureCache; } - public DefaultIndex(ResourceCache resourceCache, ProjectTree projectTree, MetricFinder metricFinder, MeasureCache measureCache) { + public DefaultIndex(ResourceCache resourceCache, DependencyPersister dependencyPersister, ProjectTree projectTree, MetricFinder metricFinder, MeasureCache measureCache) { this.resourceCache = resourceCache; - this.dependencyPersister = null; + this.dependencyPersister = dependencyPersister; this.linkPersister = null; this.eventPersister = null; this.projectTree = projectTree; @@ -205,6 +188,12 @@ public class DefaultIndex extends SonarIndex { } } + // store dependencies + for (Dependency dep : dependencies) { + dependencyPersister.saveDependency(currentProject, dep); + } + + // Keep only inter module dependencies Set<Dependency> projectDependencies = getDependenciesBetweenProjects(); dependencies.clear(); incomingDependenciesByResource.clear(); @@ -285,8 +274,10 @@ public class DefaultIndex extends SonarIndex { // Reload resources Resource from = getResource(dependency.getFrom()); Preconditions.checkArgument(from != null, dependency.getFrom() + " is not indexed"); + dependency.setFrom(from); Resource to = getResource(dependency.getTo()); Preconditions.checkArgument(to != null, dependency.getTo() + " is not indexed"); + dependency.setTo(to); Dependency existingDep = getEdge(from, to); if (existingDep != null) { @@ -297,10 +288,7 @@ public class DefaultIndex extends SonarIndex { if (parentDependency != null) { addDependency(parentDependency); } - - if (registerDependency(dependency) && dependencyPersister != null) { - dependencyPersister.saveDependency(currentProject, from, to, dependency, parentDependency); - } + registerDependency(dependency); return dependency; } @@ -476,10 +464,11 @@ public class DefaultIndex extends SonarIndex { } @Override - public Event addEvent(Resource resource, String name, String description, String category, Date date) { + public Event addEvent(Resource resource, String name, String description, String category, @Nullable Date date) { Event event = new Event(name, description, category); - event.setDate(date); - event.setCreatedAt(new Date()); + if (date != null) { + event.setDate(date); + } if (eventPersister != null) { eventPersister.saveEvent(resource, event); diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DependencyPersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/DependencyPersister.java index 15886b6b4ac..13c73e278f5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DependencyPersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DependencyPersister.java @@ -19,27 +19,46 @@ */ package org.sonar.batch.index; +import org.sonar.api.batch.sensor.dependency.internal.DefaultDependency; import org.sonar.api.database.DatabaseSession; import org.sonar.api.design.Dependency; import org.sonar.api.design.DependencyDto; import org.sonar.api.resources.Project; -import org.sonar.api.resources.Resource; +import org.sonar.batch.dependency.DependencyCache; + +import javax.annotation.Nullable; public final class DependencyPersister { - private ResourceCache resourceCache; - private DatabaseSession session; + private final ResourceCache resourceCache; + private final DatabaseSession session; + private final DependencyCache dependencyCache; - public DependencyPersister(ResourceCache resourceCache, DatabaseSession session) { + public DependencyPersister(ResourceCache resourceCache, DependencyCache dependencyCache, @Nullable DatabaseSession session) { this.resourceCache = resourceCache; + this.dependencyCache = dependencyCache; this.session = session; } - public void saveDependency(Project project, Resource from, Resource to, Dependency dependency, Dependency parentDependency) { - BatchResource fromResource = resourceCache.get(from); - BatchResource toResource = resourceCache.get(to); + public DependencyPersister(ResourceCache resourceCache, DependencyCache dependencyCache) { + this(resourceCache, dependencyCache, null); + } + + public void saveDependency(Project project, Dependency dependency) { + BatchResource fromResource = resourceCache.get(dependency.getFrom()); + BatchResource toResource = resourceCache.get(dependency.getTo()); BatchResource projectResource = resourceCache.get(project); + if (fromResource.isFile() && toResource.isFile()) { + dependencyCache.put(project.getEffectiveKey(), new DefaultDependency().setFromKey(fromResource.key()).setToKey(toResource.key()).setWeight(dependency.getWeight())); + } + + if (session != null) { + saveInDB(project, dependency, fromResource, toResource, projectResource); + } + } + + private void saveInDB(Project project, Dependency dependency, BatchResource fromResource, BatchResource toResource, BatchResource projectResource) { DependencyDto model = new DependencyDto(); model.setProjectSnapshotId(projectResource.snapshotId()); model.setUsage(dependency.getUsage()); @@ -53,8 +72,11 @@ public final class DependencyPersister { model.setToScope(toResource.resource().getScope()); model.setToSnapshotId(toResource.snapshotId()); + Dependency parentDependency = dependency.getParent(); if (parentDependency != null) { - // assume that it has been previously saved + if (parentDependency.getId() == null) { + saveDependency(project, parentDependency); + } model.setParentDependencyId(parentDependency.getId()); } session.save(model); diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/EventPersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/EventPersister.java index b01efa10063..8ee521c247e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/EventPersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/EventPersister.java @@ -22,16 +22,22 @@ package org.sonar.batch.index; import org.sonar.api.batch.Event; import org.sonar.api.database.DatabaseSession; import org.sonar.api.resources.Resource; +import org.sonar.api.utils.System2; +import java.util.Date; import java.util.List; +import static com.google.common.base.Preconditions.checkState; + public class EventPersister { + private final System2 system2; private DatabaseSession session; private ResourceCache resourceCache; - public EventPersister(DatabaseSession session, ResourceCache resourceCache) { + public EventPersister(DatabaseSession session, ResourceCache resourceCache, System2 system2) { this.session = session; this.resourceCache = resourceCache; + this.system2 = system2; } public List<Event> getEvents(Resource resource) { @@ -45,16 +51,16 @@ public class EventPersister { public void saveEvent(Resource resource, Event event) { BatchResource batchResource = resourceCache.get(resource.getEffectiveKey()); - if (batchResource == null) { - throw new IllegalStateException("Unknow component: " + resource); - } + checkState(batchResource != null, "Unknown component: " + resource); + + event.setCreatedAt(new Date(system2.now())); if (event.getDate() == null) { event.setSnapshot(batchResource.snapshot()); } else { event.setResourceId(batchResource.resource().getId()); } + session.save(event); session.commit(); - } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java index 0066e330959..66d5623c04b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/ResourcePersister.java @@ -150,7 +150,7 @@ public class ResourcePersister implements ScanPersister { snapshot = session.save(snapshot); session.commit(); - if (!permissions.hasRoles(project)) { + if (parent == null && !permissions.hasRoles(project)) { permissions.grantDefaultRoles(project); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java index 8013dd54d9d..245f85bd38d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java @@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; -import org.sonar.api.batch.sensor.dependency.Dependency; +import org.sonar.api.batch.sensor.dependency.internal.DefaultDependency; import org.sonar.api.batch.sensor.duplication.Duplication; import org.sonar.api.batch.sensor.highlighting.TypeOfText; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; @@ -92,7 +92,7 @@ public class TaskResult implements org.sonar.batch.mediumtest.ScanTaskObserver { storeDuplication(container); // storeTestCases(container); // storeCoveragePerTest(container); - // storeDependencies(container); + storeDependencies(container); } @@ -174,7 +174,7 @@ public class TaskResult implements org.sonar.batch.mediumtest.ScanTaskObserver { private void storeDependencies(ProjectScanContainer container) { DependencyCache dependencyCache = container.getComponentByType(DependencyCache.class); - for (Entry<Dependency> entry : dependencyCache.entries()) { + for (Entry<DefaultDependency> entry : dependencyCache.entries()) { String fromKey = entry.key()[1].toString(); String toKey = entry.key()[2].toString(); if (!dependencies.containsKey(fromKey)) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java b/sonar-batch/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java index 30956a0a263..137b1419d9c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java +++ b/sonar-batch/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java @@ -43,6 +43,7 @@ import org.sonar.batch.events.BatchStepHandler; import org.sonar.batch.phases.Phases; import org.sonar.batch.phases.event.PersisterExecutionHandler; import org.sonar.batch.phases.event.PersistersPhaseHandler; +import org.sonar.batch.util.BatchUtils; import javax.annotation.Nullable; @@ -118,7 +119,7 @@ public class PhasesSumUpTimeProfiler implements ProjectAnalysisHandler, SensorEx println(" -------- End of profiling of module " + module.getName() + " --------"); println(""); String fileName = module.getKey() + "-profiler.properties"; - dumpToFile(props, fileName); + dumpToFile(props, BatchUtils.cleanKeyForFilename(fileName)); totalProfiling.merge(currentModuleProfiling); if (module.isRoot() && !module.getModules().isEmpty()) { dumpTotalExecutionSummary(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java index eadf7155dc1..e3ef6349a51 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java @@ -33,6 +33,7 @@ import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.batch.bootstrap.TaskProperties; +import org.sonar.batch.util.BatchUtils; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -189,9 +190,7 @@ public class ProjectReactorBuilder { protected File initModuleWorkDir(File moduleBaseDir, Map<String, String> moduleProperties) { String workDir = moduleProperties.get(CoreProperties.WORKING_DIRECTORY); if (StringUtils.isBlank(workDir)) { - String cleanKey = StringUtils.deleteWhitespace(moduleProperties.get(CoreProperties.PROJECT_KEY_PROPERTY)); - cleanKey = StringUtils.replace(cleanKey, ":", "_"); - return new File(rootProjectWorkDir, cleanKey); + return new File(rootProjectWorkDir, BatchUtils.cleanKeyForFilename(moduleProperties.get(CoreProperties.PROJECT_KEY_PROPERTY))); } File customWorkDir = new File(workDir); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorValidator.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorValidator.java index d92dc698bea..88970aa75da 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorValidator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorValidator.java @@ -123,7 +123,7 @@ public class ProjectReactorValidator { private void validateBranch(List<String> validationMessages, @Nullable String branch) { if (StringUtils.isNotEmpty(branch) && !ComponentKeys.isValidBranch(branch)) { validationMessages.add(String.format("\"%s\" is not a valid branch name. " - + "Allowed characters are alphanumeric, '-', '_' and '.'.", branch)); + + "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch)); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index 850b6e984bf..a2f46a03fe4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -41,6 +41,7 @@ import org.sonar.batch.bootstrap.ExtensionUtils; import org.sonar.batch.bootstrap.MetricProvider; import org.sonar.batch.debt.DebtModelProvider; import org.sonar.batch.debt.IssueChangelogDebtCalculator; +import org.sonar.batch.dependency.DependencyCache; import org.sonar.batch.deprecated.components.DefaultResourceCreationLock; import org.sonar.batch.deprecated.components.PeriodsDefinition; import org.sonar.batch.duplication.DuplicationCache; @@ -189,6 +190,10 @@ public class ProjectScanContainer extends ComponentContainer { // Duplications DuplicationCache.class, + // Dependencies + DependencyPersister.class, + DependencyCache.class, + ProjectSettings.class, ScanTaskObservers.class); @@ -196,7 +201,6 @@ public class ProjectScanContainer extends ComponentContainer { private void addDataBaseComponents() { add( - DependencyPersister.class, EventPersister.class, LinkPersister.class, MeasurePersister.class, diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ExclusionFilters.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ExclusionFilters.java index e3ff5fe20e9..16441588ac9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ExclusionFilters.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ExclusionFilters.java @@ -53,6 +53,10 @@ public class ExclusionFilters implements BatchComponent { log("Excluded tests: ", testExclusions); } + public boolean hasPattern() { + return mainInclusions.length > 0 || mainExclusions.length > 0 || testInclusions.length > 0 || testExclusions.length > 0; + } + private void log(String title, PathPattern[] patterns) { if (patterns.length > 0) { LOG.info(title); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java index 47acad7208c..c5eb6bef9b4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java @@ -23,6 +23,8 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.HiddenFileFilter; import org.apache.commons.io.filefilter.IOFileFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.BatchComponent; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.InputFile; @@ -52,6 +54,8 @@ import java.util.concurrent.TimeUnit; */ public class FileIndexer implements BatchComponent { + private static final Logger LOG = LoggerFactory.getLogger(FileIndexer.class); + private static final IOFileFilter DIR_FILTER = FileFilterUtils.and(HiddenFileFilter.VISIBLE, FileFilterUtils.notFileFilter(FileFilterUtils.prefixFileFilter("."))); private static final IOFileFilter FILE_FILTER = HiddenFileFilter.VISIBLE; @@ -94,6 +98,10 @@ public class FileIndexer implements BatchComponent { waitForTasksToComplete(); progressReport.stop(progress.count() + " files indexed"); + + if (exclusionFilters.hasPattern()) { + LOG.info(progress.excludedByPatternsCount() + " files ignored because of inclusion/exclusion patterns"); + } } private void waitForTasksToComplete() { @@ -134,6 +142,8 @@ public class FileIndexer implements BatchComponent { inputFile.setModuleBaseDir(fileSystem.baseDirPath()); if (exclusionFilters.accept(inputFile, type)) { indexFile(inputFileBuilder, fileSystem, progress, inputFile, type); + } else { + progress.increaseExcludedByPatternsCount(); } } } @@ -174,6 +184,7 @@ public class FileIndexer implements BatchComponent { private class Progress { private final Set<Path> indexed = new HashSet<>(); + private int excludedByPatternsCount = 0; synchronized void markAsIndexed(InputFile inputFile) { if (indexed.contains(inputFile.path())) { @@ -184,6 +195,14 @@ public class FileIndexer implements BatchComponent { progressReport.message(indexed.size() + " files indexed... (last one was " + inputFile.relativePath() + ")"); } + void increaseExcludedByPatternsCount() { + excludedByPatternsCount++; + } + + public int excludedByPatternsCount() { + return excludedByPatternsCount; + } + int count() { return indexed.size(); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java index 654134b0519..7dd0c1eab1c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java @@ -26,7 +26,7 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorStorage; -import org.sonar.api.batch.sensor.dependency.Dependency; +import org.sonar.api.batch.sensor.dependency.NewDependency; import org.sonar.api.batch.sensor.dependency.internal.DefaultDependency; import org.sonar.api.batch.sensor.duplication.NewDuplication; import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplication; @@ -43,7 +43,6 @@ import org.sonar.api.batch.sensor.test.internal.DefaultCoverage; import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseCoverage; import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseExecution; import org.sonar.api.config.Settings; -import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.highlighting.DefaultHighlightingBuilder; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.symbol.DefaultSymbolTableBuilder; @@ -56,18 +55,16 @@ public class DefaultSensorContext implements SensorContext { private final FileSystem fs; private final ActiveRules activeRules; private final ComponentDataCache componentDataCache; - private final DuplicationCache duplicationCache; private final SensorStorage sensorStorage; private final AnalysisMode analysisMode; public DefaultSensorContext(Settings settings, FileSystem fs, ActiveRules activeRules, AnalysisMode analysisMode, ComponentDataCache componentDataCache, - DuplicationCache duplicationCache, SensorStorage sensorStorage) { + SensorStorage sensorStorage) { this.settings = settings; this.fs = fs; this.activeRules = activeRules; this.analysisMode = analysisMode; this.componentDataCache = componentDataCache; - this.duplicationCache = duplicationCache; this.sensorStorage = sensorStorage; } @@ -132,7 +129,7 @@ public class DefaultSensorContext implements SensorContext { } @Override - public Dependency newDependency() { + public NewDependency newDependency() { return new DefaultDependency(sensorStorage); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java index 300fe651380..d512c02ca96 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java +++ b/sonar-batch/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java @@ -58,6 +58,7 @@ import org.sonar.api.test.Testable; import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.index.ResourceCache; import org.sonar.batch.sensor.coverage.CoverageExclusions; import org.sonar.core.component.ComponentKeys; @@ -70,17 +71,20 @@ public class DefaultSensorStorage implements SensorStorage { private final DefaultIndex sonarIndex; private final CoverageExclusions coverageExclusions; private final DuplicationCache duplicationCache; + private final ResourceCache resourceCache; public DefaultSensorStorage(MetricFinder metricFinder, Project project, ResourcePerspectives perspectives, Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache, - DuplicationCache duplicationCache, DefaultIndex sonarIndex, CoverageExclusions coverageExclusions) { + DuplicationCache duplicationCache, DefaultIndex sonarIndex, CoverageExclusions coverageExclusions, + ResourceCache resourceCache) { this.metricFinder = metricFinder; this.project = project; this.perspectives = perspectives; this.sonarIndex = sonarIndex; this.coverageExclusions = coverageExclusions; this.duplicationCache = duplicationCache; + this.resourceCache = resourceCache; } private Metric findMetricOrFail(String metricKey) { @@ -241,10 +245,10 @@ public class DefaultSensorStorage implements SensorStorage { @Override public void store(org.sonar.api.batch.sensor.dependency.Dependency dep) { - File fromResource = getFile(dep.from()); - File toResource = getFile(dep.to()); + File fromResource = (File) resourceCache.get(dep.fromKey()).resource(); + File toResource = (File) resourceCache.get(dep.toKey()).resource(); if (sonarIndex.getEdge(fromResource, toResource) != null) { - throw new IllegalStateException("Dependency between " + dep.from() + " and " + dep.to() + " was already saved."); + throw new IllegalStateException("Dependency between " + dep.fromKey() + " and " + dep.toKey() + " was already saved."); } Directory fromParent = fromResource.getParent(); Directory toParent = toResource.getParent(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java b/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java new file mode 100644 index 00000000000..e6321ac8b3b --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java @@ -0,0 +1,37 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.util; + +import org.apache.commons.lang.StringUtils; + +public class BatchUtils { + + private BatchUtils() { + } + + /** + * Clean provided string to remove chars that are not valid as file name. + * @param projectKey e.g. my:file + */ + public static String cleanKeyForFilename(String projectKey) { + String cleanKey = StringUtils.deleteWhitespace(projectKey); + return StringUtils.replace(cleanKey, ":", "_"); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/cpd/CpdComponentsTest.java b/sonar-batch/src/test/java/org/sonar/batch/cpd/CpdComponentsTest.java index 90632426ee3..1b5f9310b0a 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cpd/CpdComponentsTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cpd/CpdComponentsTest.java @@ -27,6 +27,6 @@ public class CpdComponentsTest { @Test public void getExtensions() { - assertThat(CpdComponents.all()).hasSize(10); + assertThat(CpdComponents.all().size()).isGreaterThan(0); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/design/DirectoryDsmDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/design/DirectoryDsmDecoratorTest.java index fa3ceecc3a5..7cc8ca87bc1 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/design/DirectoryDsmDecoratorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/design/DirectoryDsmDecoratorTest.java @@ -97,12 +97,16 @@ public class DirectoryDsmDecoratorTest { public void testDirectoryDsmDecoratorNoDependency() { decorator.decorate(dir, dirContext); - verify(dirContext, times(4)).saveMeasure(any(Measure.class)); + verify(dirContext, times(5)).saveMeasure(any(Measure.class)); verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_CYCLES, 0.0)); verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_FEEDBACK_EDGES, 0.0)); verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_TANGLES, 0.0)); verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_EDGES_WEIGHT, 0.0)); + verify(dirContext).saveMeasure( + isMeasureWithValue(CoreMetrics.DEPENDENCY_MATRIX, + "[{\"i\":1,\"n\":\"Foo1.java\",\"q\":\"FIL\",\"v\":[{},{}]},{\"i\":2,\"n\":\"Foo2.java\",\"q\":\"FIL\",\"v\":[{},{}]}]")); + } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/design/ProjectDsmDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/design/ProjectDsmDecoratorTest.java index c18da1fdde9..0d78693fa59 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/design/ProjectDsmDecoratorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/design/ProjectDsmDecoratorTest.java @@ -19,7 +19,6 @@ */ package org.sonar.batch.design; -import edu.emory.mathcs.backport.java.util.Collections; import org.apache.commons.lang.ObjectUtils; import org.junit.Before; import org.junit.Test; @@ -87,17 +86,6 @@ public class ProjectDsmDecoratorTest { Project child = new Project("child").setParent(p); decorator.decorate(p, rootContext); - // Should not do anything if module has no dir - when(rootContext.getChildren()).thenReturn(Collections.emptyList()); - decorator.decorate(root, rootContext); - - verify(rootContext, never()).saveMeasure(any(Measure.class)); - } - - @Test - public void testProjectDsmDecoratorNoDependency() { - decorator.decorate(root, rootContext); - verify(rootContext, never()).saveMeasure(any(Measure.class)); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/design/SubProjectDsmDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/design/SubProjectDsmDecoratorTest.java index a9a41264adf..bc65050aab2 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/design/SubProjectDsmDecoratorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/design/SubProjectDsmDecoratorTest.java @@ -96,12 +96,14 @@ public class SubProjectDsmDecoratorTest { public void testSubProjectDsmDecoratorNoDependency() { decorator.decorate(module, moduleContext); - verify(moduleContext, times(4)).saveMeasure(any(Measure.class)); + verify(moduleContext, times(5)).saveMeasure(any(Measure.class)); verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_CYCLES, 0.0)); verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_FEEDBACK_EDGES, 0.0)); verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_TANGLES, 0.0)); verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_EDGES_WEIGHT, 0.0)); + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DEPENDENCY_MATRIX, + "[{\"i\":1,\"n\":\"src/foo1\",\"q\":\"DIR\",\"v\":[{},{}]},{\"i\":2,\"n\":\"src/foo2\",\"q\":\"DIR\",\"v\":[{},{}]}]")); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/CacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/CacheTest.java index 9bae728f375..0235af3dc89 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/CacheTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/CacheTest.java @@ -25,6 +25,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.sonar.batch.index.Cache.Entry; import static org.assertj.core.api.Assertions.assertThat; @@ -57,11 +58,15 @@ public class CacheTest { assertThat(cache.get("france")).isEqualTo("paris"); assertThat(cache.keySet()).containsOnly("france", "italy"); assertThat(cache.keySet("france")).isEmpty(); - assertThat(cache.values()).containsOnly("paris", "rome"); + Iterable<String> values = cache.values(); + assertThat(values).containsOnly("paris", "rome"); + assertThat(values).containsOnly("paris", "rome"); assertThat(cache.containsKey("france")).isTrue(); - Cache.Entry[] entries = Iterables.toArray(cache.entries(), Cache.Entry.class); + Iterable<Entry<String>> iterable = cache.entries(); + Cache.Entry[] entries = Iterables.toArray(iterable, Cache.Entry.class); assertThat(entries).hasSize(2); + assertThat(iterable).hasSize(2); assertThat(entries[0].key()[0]).isEqualTo("france"); assertThat(entries[0].value()).isEqualTo("paris"); assertThat(entries[1].key()[0]).isEqualTo("italy"); @@ -74,10 +79,10 @@ public class CacheTest { assertThat(cache.keySet("france")).isEmpty(); assertThat(cache.containsKey("france")).isFalse(); assertThat(cache.containsKey("italy")).isTrue(); - assertThat(cache.values()).containsOnly("rome"); + assertThat(values).containsOnly("rome"); cache.clear(); - assertThat(cache.values()).isEmpty(); + assertThat(values).isEmpty(); } @Test @@ -114,8 +119,10 @@ public class CacheTest { assertThat(cache.values("europe")).containsOnly("paris", "rome"); assertThat(cache.values("oceania")).isEmpty(); - Cache.Entry[] allEntries = Iterables.toArray(cache.entries(), Cache.Entry.class); + Iterable<Entry<String>> iterable = cache.entries(); + Cache.Entry[] allEntries = Iterables.toArray(iterable, Cache.Entry.class); assertThat(allEntries).hasSize(3); + assertThat(iterable).hasSize(3); assertThat(allEntries[0].key()).isEqualTo(new String[] {"asia", "china"}); assertThat(allEntries[0].value()).isEqualTo("pekin"); assertThat(allEntries[1].key()).isEqualTo(new String[] {"europe", "france"}); @@ -123,8 +130,10 @@ public class CacheTest { assertThat(allEntries[2].key()).isEqualTo(new String[] {"europe", "italy"}); assertThat(allEntries[2].value()).isEqualTo("rome"); - Cache.Entry[] subEntries = Iterables.toArray(cache.entries("europe"), Cache.Entry.class); + Iterable<Entry<String>> iterable2 = cache.entries("europe"); + Cache.Entry[] subEntries = Iterables.toArray(iterable2, Cache.Entry.class); assertThat(subEntries).hasSize(2); + assertThat(iterable2).hasSize(2); assertThat(subEntries[0].key()).isEqualTo(new String[] {"europe", "france"}); assertThat(subEntries[0].value()).isEqualTo("paris"); assertThat(subEntries[1].key()).isEqualTo(new String[] {"europe", "italy"}); @@ -171,8 +180,10 @@ public class CacheTest { assertThat(cache.values("europe")).containsOnly("eiffel tower", "lake", "colosseum", "notre dame"); assertThat(cache.values("europe", "france")).containsOnly("eiffel tower", "lake", "notre dame"); - Cache.Entry[] allEntries = Iterables.toArray(cache.entries(), Cache.Entry.class); + Iterable<Entry<String>> iterable = cache.entries(); + Cache.Entry[] allEntries = Iterables.toArray(iterable, Cache.Entry.class); assertThat(allEntries).hasSize(7); + assertThat(iterable).hasSize(7); assertThat(allEntries[0].key()).isEqualTo(new String[] {"america", "us", "new york"}); assertThat(allEntries[0].value()).isEqualTo("empire state building"); assertThat(allEntries[1].key()).isEqualTo(new String[] {"asia", "china", "pekin"}); @@ -186,8 +197,10 @@ public class CacheTest { assertThat(allEntries[5].key()).isEqualTo(new String[] {"europe", "italy", "rome"}); assertThat(allEntries[5].value()).isEqualTo("colosseum"); - Cache.Entry[] subEntries = Iterables.toArray(cache.entries("europe"), Cache.Entry.class); + Iterable<Entry<String>> iterable2 = cache.entries("europe"); + Cache.Entry[] subEntries = Iterables.toArray(iterable2, Cache.Entry.class); assertThat(subEntries).hasSize(4); + assertThat(iterable2).hasSize(4); assertThat(subEntries[0].key()).isEqualTo(new String[] {"europe", "france", "annecy"}); assertThat(subEntries[0].value()).isEqualTo("lake"); assertThat(subEntries[1].key()).isEqualTo(new String[] {"europe", "france", "paris"}); diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/ResourcePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/ResourcePersisterTest.java index 828b660613f..fee743895f0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/ResourcePersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/ResourcePersisterTest.java @@ -384,6 +384,19 @@ public class ResourcePersisterTest extends AbstractDbUnitTestCase { } @Test + public void shouldNotGrantDefaultPermissionsOnModules() { + setupData("shared"); + resourceCache.add(multiModuleProject, null).setSnapshot(persister.persist(null, multiModuleProject, null)); + resourceCache.add(moduleA, multiModuleProject).setSnapshot(persister.persist(null, moduleA, multiModuleProject)); + when(permissions.hasRoles(multiModuleProject)).thenReturn(true); + persister.persist(null, multiModuleProject, null); + + persister.persist(null, moduleA, multiModuleProject); + + verify(permissions, never()).grantDefaultRoles(moduleA); + } + + @Test public void shouldNotGrantDefaultPermissionsIfExistingProject() { setupData("shared"); diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java index 47ebaa81f8a..cc940ae1361 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java @@ -30,6 +30,9 @@ import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.sensor.duplication.Duplication; +import org.sonar.api.batch.sensor.measure.Measure; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.measures.CoreMetrics; import org.sonar.batch.mediumtest.BatchMediumTester; import org.sonar.batch.mediumtest.TaskResult; import org.sonar.xoo.XooPlugin; @@ -129,6 +132,44 @@ public class CpdMediumTest { assertThat(cloneGroupFile2.duplicates().get(0).resourceKey()).isEqualTo(((DefaultInputFile) inputFile1).key()); } + // SONAR-6000 + @Test + public void truncateDuplication() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + String duplicatedStuff = "Sample xoo\n"; + + int blockCount = 10000; + File xooFile1 = new File(srcDir, "sample.xoo"); + for (int i = 0; i < blockCount; i++) { + FileUtils.write(xooFile1, duplicatedStuff, true); + FileUtils.write(xooFile1, "" + i + "\n", true); + } + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .put("sonar.cpd.xoo.minimumTokens", "1") + .put("sonar.cpd.xoo.minimumLines", "1") + .build()) + .start(); + + Measure duplicatedBlocks = null; + for (Measure m : result.measures()) { + if (m.metric().key().equals("duplicated_blocks")) { + duplicatedBlocks = m; + } + } + assertThat(duplicatedBlocks.value()).isEqualTo(blockCount); + + List<Duplication> duplicationGroups = result.duplicationsFor(result.inputFile("src/sample.xoo")); + assertThat(duplicationGroups).hasSize(1); + + Duplication cloneGroup = duplicationGroups.get(0); + assertThat(cloneGroup.duplicates()).hasSize(100); + } + @Test public void testIntraFileDuplications() throws IOException { File srcDir = new File(baseDir, "src"); @@ -164,10 +205,10 @@ public class CpdMediumTest { assertThat(cloneGroup.duplicates().get(0).startLine()).isEqualTo(5); assertThat(cloneGroup.duplicates().get(0).length()).isEqualTo(2); - // assertThat(result.measures()).contains(new DefaultMeasure<String>() - // .forMetric(CoreMetrics.DUPLICATION_LINES_DATA) - // .onFile(inputFile) - // .withValue("1=1;2=1;3=0;4=0;5=1;6=1;7=0")); + assertThat(result.measures()).contains(new DefaultMeasure<String>() + .forMetric(CoreMetrics.DUPLICATION_LINES_DATA) + .onFile(inputFile) + .withValue("1=1;2=1;3=0;4=0;5=1;6=1;7=0")); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/dependency/DependencyMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/dependency/DependencyMediumTest.java index 4d562d4de87..e7a08a4a37c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/dependency/DependencyMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/dependency/DependencyMediumTest.java @@ -34,6 +34,8 @@ import org.sonar.xoo.XooPlugin; import java.io.File; import java.io.IOException; +import static org.assertj.core.api.Assertions.assertThat; + public class DependencyMediumTest { @Rule @@ -85,8 +87,8 @@ public class DependencyMediumTest { .build()) .start(); - // assertThat(result.dependencyWeight(result.inputFile("src/sample.xoo"), result.inputFile("src/sample2.xoo"))).isEqualTo(3); - // assertThat(result.dependencyWeight(result.inputFile("src/sample.xoo"), result.inputFile("src/foo/sample3.xoo"))).isEqualTo(6); + assertThat(result.dependencyWeight(result.inputFile("src/sample.xoo"), result.inputFile("src/sample2.xoo"))).isEqualTo(3); + assertThat(result.dependencyWeight(result.inputFile("src/sample.xoo"), result.inputFile("src/foo/sample3.xoo"))).isEqualTo(6); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java index 03c40181906..72023e2e180 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java @@ -173,6 +173,40 @@ public class FileSystemMediumTest { } @Test + public void fileInclusionsExclusions() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + File xooFile2 = new File(baseDir, "another.xoo"); + FileUtils.write(xooFile2, "Sample xoo 2\ncontent"); + + File testDir = new File(baseDir, "test"); + testDir.mkdir(); + + File xooTestFile = new File(baseDir, "sampleTest2.xoo"); + FileUtils.write(xooTestFile, "Sample test xoo\ncontent"); + + File xooTestFile2 = new File(testDir, "sampleTest.xoo"); + FileUtils.write(xooTestFile2, "Sample test xoo 2\ncontent"); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src,another.xoo") + .put("sonar.tests", "test,sampleTest2.xoo") + .put("sonar.inclusions", "src/**") + .put("sonar.exclusions", "**/another.*") + .put("sonar.test.inclusions", "**/sampleTest*.*") + .put("sonar.test.exclusions", "**/sampleTest2.xoo") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(2); + } + + @Test public void failForDuplicateInputFile() throws IOException { File srcDir = new File(baseDir, "src"); srcDir.mkdir(); diff --git a/sonar-batch/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java b/sonar-batch/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java index 2802d2f7058..e04c6843313 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java @@ -81,7 +81,7 @@ public class PhasesSumUpTimeProfilerTest { @Test public void testSimpleProject() throws InterruptedException { - final Project project = mockProject("project", true); + final Project project = mockProject("my:project", true); when(project.getModules()).thenReturn(Collections.<Project>emptyList()); fakeAnalysis(profiler, project); diff --git a/sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorContextTest.java b/sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorContextTest.java index 4f7fa7507ac..19b12f7b972 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorContextTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorContextTest.java @@ -32,7 +32,6 @@ import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; import org.sonar.api.batch.sensor.SensorStorage; import org.sonar.api.config.Settings; import org.sonar.api.measures.CoreMetrics; -import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.index.ComponentDataCache; import static org.assertj.core.api.Assertions.assertThat; @@ -65,7 +64,7 @@ public class DefaultSensorContextTest { ComponentDataCache componentDataCache = mock(ComponentDataCache.class); sensorStorage = mock(SensorStorage.class); analysisMode = mock(AnalysisMode.class); - adaptor = new DefaultSensorContext(settings, fs, activeRules, analysisMode, componentDataCache, mock(DuplicationCache.class), sensorStorage); + adaptor = new DefaultSensorContext(settings, fs, activeRules, analysisMode, componentDataCache, sensorStorage); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java b/sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java index a4ed0b08571..02b526f0375 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java @@ -54,6 +54,7 @@ import org.sonar.api.rule.RuleKey; import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.index.ResourceCache; import org.sonar.batch.sensor.coverage.CoverageExclusions; import static org.assertj.core.api.Assertions.assertThat; @@ -80,6 +81,8 @@ public class DefaultSensorStorageTest { private Project project; private DefaultIndex sonarIndex; + private ResourceCache resourceCache; + @Before public void prepare() throws Exception { activeRules = new ActiveRulesBuilder().build(); @@ -94,8 +97,9 @@ public class DefaultSensorStorageTest { sonarIndex = mock(DefaultIndex.class); CoverageExclusions coverageExclusions = mock(CoverageExclusions.class); when(coverageExclusions.accept(any(Resource.class), any(Measure.class))).thenReturn(true); + resourceCache = new ResourceCache(); sensorStorage = new DefaultSensorStorage(metricFinder, project, - resourcePerspectives, settings, fs, activeRules, componentDataCache, mock(DuplicationCache.class), sonarIndex, coverageExclusions); + resourcePerspectives, settings, fs, activeRules, componentDataCache, mock(DuplicationCache.class), sonarIndex, coverageExclusions, resourceCache); } @Test @@ -248,10 +252,10 @@ public class DefaultSensorStorageTest { @Test public void shouldStoreDependencyInSameFolder() { - File foo = File.create("src/Foo.java"); - File bar = File.create("src/Bar.java"); - when(sonarIndex.getResource(foo)).thenReturn(foo); - when(sonarIndex.getResource(bar)).thenReturn(bar); + Resource foo = File.create("src/Foo.java").setEffectiveKey("foo:src/Foo.java"); + Resource bar = File.create("src/Bar.java").setEffectiveKey("foo:src/Bar.java"); + resourceCache.add(foo, null); + resourceCache.add(bar, null); sensorStorage.store(new DefaultDependency() .from(new DefaultInputFile("foo", "src/Foo.java").setType(Type.MAIN)) @@ -270,14 +274,15 @@ public class DefaultSensorStorageTest { @Test public void throw_if_attempt_to_save_same_dep_twice() { - File foo = File.create("src/Foo.java"); - File bar = File.create("src/Bar.java"); - when(sonarIndex.getResource(foo)).thenReturn(foo); - when(sonarIndex.getResource(bar)).thenReturn(bar); + Resource foo = File.create("src/Foo.java").setEffectiveKey("foo:src/Foo.java"); + Resource bar = File.create("src/Bar.java").setEffectiveKey("foo:src/Bar.java"); + resourceCache.add(foo, null); + resourceCache.add(bar, null); + when(sonarIndex.getEdge(foo, bar)).thenReturn(new Dependency(foo, bar)); thrown.expect(IllegalStateException.class); - thrown.expectMessage("Dependency between [moduleKey=foo, relative=src/Foo.java, basedir=null] and [moduleKey=foo, relative=src/Bar.java, basedir=null] was already saved."); + thrown.expectMessage("Dependency between foo:src/Foo.java and foo:src/Bar.java was already saved."); sensorStorage.store(new DefaultDependency() .from(new DefaultInputFile("foo", "src/Foo.java").setType(Type.MAIN)) @@ -288,10 +293,10 @@ public class DefaultSensorStorageTest { @Test public void shouldStoreDependencyInDifferentFolder() { - File foo = File.create("src1/Foo.java"); - File bar = File.create("src2/Bar.java"); - when(sonarIndex.getResource(foo)).thenReturn(foo); - when(sonarIndex.getResource(bar)).thenReturn(bar); + Resource foo = File.create("src1/Foo.java").setEffectiveKey("foo:src1/Foo.java"); + Resource bar = File.create("src2/Bar.java").setEffectiveKey("foo:src2/Bar.java"); + resourceCache.add(foo, null); + resourceCache.add(bar, null); sensorStorage.store(new DefaultDependency() .from(new DefaultInputFile("foo", "src1/Foo.java").setType(Type.MAIN)) @@ -318,12 +323,14 @@ public class DefaultSensorStorageTest { @Test public void shouldIncrementParentWeight() { - File foo = File.create("src1/Foo.java"); - File bar = File.create("src2/Bar.java"); - Directory src1 = Directory.create("src1"); - Directory src2 = Directory.create("src2"); - when(sonarIndex.getResource(foo)).thenReturn(foo); - when(sonarIndex.getResource(bar)).thenReturn(bar); + Resource src1 = Directory.create("src1").setEffectiveKey("foo:src1"); + Resource src2 = Directory.create("src2").setEffectiveKey("foo:src2"); + Resource foo = File.create("src1/Foo.java").setEffectiveKey("foo:src1/Foo.java"); + Resource bar = File.create("src2/Bar.java").setEffectiveKey("foo:src2/Bar.java"); + resourceCache.add(src1, null); + resourceCache.add(src2, null); + resourceCache.add(foo, src1); + resourceCache.add(bar, src2); Dependency parentDep = new Dependency(src1, src2).setWeight(4); when(sonarIndex.getEdge(src1, src2)).thenReturn(parentDep); diff --git a/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/no-previous-version.xml b/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/no-previous-version.xml index 2f01f86b989..480b91f147e 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/no-previous-version.xml +++ b/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/no-previous-version.xml @@ -30,13 +30,13 @@ scope="PRJ" qualifier="TRK" created_at="1226235480000" build_date="1226235480000" version="1.2-SNAPSHOT" path="" status="U" islast="true" depth="0" /> - <events id="2" name="Foo" resource_id="1" snapshot_id="1000" category="Other" event_date="2008-11-03 13:58:00.00" created_at="2008-11-03 13:58:00.00" description="" + <events id="2" name="Foo" resource_id="1" snapshot_id="1000" category="Other" event_date="1225717080000" created_at="1225717080000" description="" event_data="[null]"/> - <events id="4" name="Bar" resource_id="1" snapshot_id="1001" category="Other" event_date="2008-11-05 13:58:00.00" created_at="2008-11-05 13:58:00.00" description="" + <events id="4" name="Bar" resource_id="1" snapshot_id="1001" category="Other" event_date="1225889880000" created_at="1225889880000" description="" event_data="[null]"/> - <events id="5" name="Uhh" resource_id="1" snapshot_id="1002" category="Other" event_date="2008-11-07 13:58:00.00" created_at="2008-11-07 13:58:00.00" description="" + <events id="5" name="Uhh" resource_id="1" snapshot_id="1002" category="Other" event_date="1226062680000" created_at="1226062680000" description="" event_data="[null]"/> - <events id="6" name="1.2-SNAPSHOT" resource_id="1" snapshot_id="1003" category="Version" event_date="2008-11-09 13:58:00.00" created_at="2008-11-09 13:58:00.00" description="" + <events id="6" name="1.2-SNAPSHOT" resource_id="1" snapshot_id="1003" category="Version" event_date="1226235480000" created_at="1226235480000" description="" event_data="[null]"/> </dataset> diff --git a/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/with-previous-version-deleted.xml b/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/with-previous-version-deleted.xml index d990e648414..bc3a3c9c055 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/with-previous-version-deleted.xml +++ b/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/with-previous-version-deleted.xml @@ -30,17 +30,17 @@ scope="PRJ" qualifier="TRK" created_at="1226235480000" build_date="1226235480000" version="1.2-SNAPSHOT" path="" status="U" islast="true" depth="0" /> - <events id="1" name="1.0" resource_id="1" snapshot_id="1000" category="Version" event_date="2008-11-02 13:58:00.00" created_at="2008-11-02 13:58:00.00" description="" + <events id="1" name="1.0" resource_id="1" snapshot_id="1000" category="Version" event_date="1225630680000" created_at="1225630680000" description="" event_data="[null]"/> - <events id="2" name="Foo" resource_id="1" snapshot_id="1000" category="Other" event_date="2008-11-03 13:58:00.00" created_at="2008-11-03 13:58:00.00" description="" + <events id="2" name="Foo" resource_id="1" snapshot_id="1000" category="Other" event_date="1225717080000" created_at="1225717080000" description="" event_data="[null]"/> <!-- The "1.1" version was deleted from the history : --> <!-- events id="3" name="1.1" resource_id="1" snapshot_id="1001" category="Version" event_date="2008-11-04 13:58:00.00" created_at="2008-11-04 13:58:00.00" description=""/--> - <events id="4" name="Bar" resource_id="1" snapshot_id="1001" category="Other" event_date="2008-11-05 13:58:00.00" created_at="2008-11-05 13:58:00.00" description="" + <events id="4" name="Bar" resource_id="1" snapshot_id="1001" category="Other" event_date="1225889880000" created_at="1225889880000" description="" event_data="[null]"/> - <events id="5" name="Uhh" resource_id="1" snapshot_id="1002" category="Other" event_date="2008-11-07 13:58:00.00" created_at="2008-11-07 13:58:00.00" description="" + <events id="5" name="Uhh" resource_id="1" snapshot_id="1002" category="Other" event_date="1226062680000" created_at="1226062680000" description="" event_data="[null]"/> - <events id="6" name="1.2-SNAPSHOT" resource_id="1" snapshot_id="1003" category="Version" event_date="2008-11-09 13:58:00.00" created_at="2008-11-09 13:58:00.00" description="" + <events id="6" name="1.2-SNAPSHOT" resource_id="1" snapshot_id="1003" category="Version" event_date="1226235480000" created_at="1226235480000" description="" event_data="[null]"/> </dataset> diff --git a/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/with-previous-version.xml b/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/with-previous-version.xml index 5ecbc2fa959..6efee1a0f51 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/with-previous-version.xml +++ b/sonar-batch/src/test/resources/org/sonar/batch/deprecated/components/PastSnapshotFinderByPreviousVersionTest/with-previous-version.xml @@ -30,11 +30,11 @@ scope="PRJ" qualifier="TRK" created_at="1226235480000" build_date="1226235480000" version="1.2-SNAPSHOT" path="" status="U" islast="true" depth="0" /> - <events id="1" name="1.0" resource_id="1" snapshot_id="1000" category="Version" event_date="2008-11-02 13:58:00.00" created_at="2008-11-02 13:58:00.00" description="" event_data="[null]"/> - <events id="2" name="Foo" resource_id="1" snapshot_id="1000" category="Other" event_date="2008-11-03 13:58:00.00" created_at="2008-11-03 13:58:00.00" description="" event_data="[null]"/> - <events id="3" name="1.1" resource_id="1" snapshot_id="1001" category="Version" event_date="2008-11-04 13:58:00.00" created_at="2008-11-04 13:58:00.00" description="" event_data="[null]"/> - <events id="4" name="Bar" resource_id="1" snapshot_id="1001" category="Other" event_date="2008-11-05 13:58:00.00" created_at="2008-11-05 13:58:00.00" description="" event_data="[null]"/> - <events id="5" name="Uhh" resource_id="1" snapshot_id="1002" category="Other" event_date="2008-11-07 13:58:00.00" created_at="2008-11-07 13:58:00.00" description="" event_data="[null]"/> - <events id="6" name="1.2-SNAPSHOT" resource_id="1" snapshot_id="1003" category="Version" event_date="2008-11-09 13:58:00.00" created_at="2008-11-09 13:58:00.00" description="" event_data="[null]"/> + <events id="1" name="1.0" resource_id="1" snapshot_id="1000" category="Version" event_date="1225630680000" created_at="1225630680000" description="" event_data="[null]"/> + <events id="2" name="Foo" resource_id="1" snapshot_id="1000" category="Other" event_date="1225717080000" created_at="1225717080000" description="" event_data="[null]"/> + <events id="3" name="1.1" resource_id="1" snapshot_id="1001" category="Version" event_date="1225803480000" created_at="1225803480000" description="" event_data="[null]"/> + <events id="4" name="Bar" resource_id="1" snapshot_id="1001" category="Other" event_date="1225889880000" created_at="1225889880000" description="" event_data="[null]"/> + <events id="5" name="Uhh" resource_id="1" snapshot_id="1002" category="Other" event_date="1226062680000" created_at="1226062680000" description="" event_data="[null]"/> + <events id="6" name="1.2-SNAPSHOT" resource_id="1" snapshot_id="1003" category="Version" event_date="1226235480000" created_at="1226235480000" description="" event_data="[null]"/> </dataset> diff --git a/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java b/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java index b9b5fb61955..97977cd2162 100644 --- a/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java +++ b/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java @@ -22,6 +22,7 @@ package org.sonar.core.config; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.sonar.api.CoreProperties; +import org.sonar.api.PropertyType; import org.sonar.api.config.PropertyDefinition; import org.sonar.api.resources.Qualifiers; import org.sonar.core.computation.dbcleaner.DataCleanerProperties; @@ -118,8 +119,41 @@ public class CorePropertyDefinitions { .onlyOnQualifiers(Qualifiers.PROJECT) .category(CoreProperties.CATEGORY_GENERAL) .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) + .build(), + + // CPD + PropertyDefinition.builder(CoreProperties.CPD_CROSS_PROJECT) + .defaultValue(CoreProperties.CPD_CROSS_RPOJECT_DEFAULT_VALUE + "") + .name("Cross project duplication detection") + .description("By default, SonarQube detects duplications at sub-project level. This means that a block " + + "duplicated on two sub-projects of the same project won't be reported. Setting this parameter to \"true\" " + + "allows to detect duplicates across sub-projects and more generally across projects. Note that activating " + + "this property will slightly increase each SonarQube analysis time.") + .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) + .category(CoreProperties.CATEGORY_GENERAL) + .subCategory(CoreProperties.SUBCATEGORY_DUPLICATIONS) + .type(PropertyType.BOOLEAN) + .build(), + PropertyDefinition.builder(CoreProperties.CPD_SKIP_PROPERTY) + .defaultValue("false") + .name("Skip") + .description("Disable detection of duplications") + .hidden() + .category(CoreProperties.CATEGORY_GENERAL) + .subCategory(CoreProperties.SUBCATEGORY_DUPLICATIONS) + .type(PropertyType.BOOLEAN) + .build(), + PropertyDefinition.builder(CoreProperties.CPD_EXCLUSIONS) + .defaultValue("") + .name("Duplication Exclusions") + .description("Patterns used to exclude some source files from the duplication detection mechanism. " + + "See below to know how to use wildcards to specify this property.") + .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_DUPLICATIONS_EXCLUSIONS) + .multiValues(true) .build() - )); + )); return defs; } } diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java index 048e602afe9..7bc6687dd22 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java @@ -33,7 +33,7 @@ import java.util.List; */ public class DatabaseVersion implements BatchComponent, ServerComponent { - public static final int LAST_VERSION = 790; + public static final int LAST_VERSION = 794; /** * List of all the tables.n @@ -88,7 +88,7 @@ public class DatabaseVersion implements BatchComponent, ServerComponent { "user_roles", "widgets", "widget_properties" - ); + ); private MyBatis mybatis; public DatabaseVersion(MyBatis mybatis) { diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql index af6b0b2df00..ef5ff49f785 100644 --- a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql @@ -317,6 +317,10 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('787'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('788'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('789'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('790'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('791'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('792'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('793'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('794'); INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '1418215735482', '1418215735482', null, null); ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl index 484d9265b83..86d2134ef54 100644 --- a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl @@ -167,8 +167,8 @@ CREATE TABLE "EVENTS" ( "RESOURCE_ID" INTEGER, "SNAPSHOT_ID" INTEGER, "CATEGORY" VARCHAR(50), - "EVENT_DATE" TIMESTAMP, - "CREATED_AT" TIMESTAMP, + "EVENT_DATE" BIGINT NOT NULL, + "CREATED_AT" BIGINT NOT NULL, "DESCRIPTION" VARCHAR(4000), "EVENT_DATA" VARCHAR(4000) ); diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index c683d372e66..fe991e780d1 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2884,7 +2884,13 @@ component_viewer.workspace.hide_workspace=Hide workspace source_viewer.covered=Covered source_viewer.not_covered=Not covered source_viewer.conditions=conditions -source_viewer.expand_duplications=Click to display more in details all the duplicated blocks of this file + +source_viewer.tooltip.duplicated_line=This line is duplicated. Click to see duplicated blocks. +source_viewer.tooltip.duplicated_block=Duplicated block. Click for details. +source_viewer.tooltip.covered=Fully covered by tests. Click for details. +source_viewer.tooltip.partially-covered=Partially covered by tests. Click for details. +source_viewer.tooltip.uncovered=Not covered by tests. +source_viewer.tooltip.new_code=New {0}. #------------------------------------------------------------------------------ diff --git a/sonar-core/src/test/java/org/sonar/core/purge/PurgeCommandsTest.java b/sonar-core/src/test/java/org/sonar/core/purge/PurgeCommandsTest.java index d7d285e42fa..693ea65a447 100644 --- a/sonar-core/src/test/java/org/sonar/core/purge/PurgeCommandsTest.java +++ b/sonar-core/src/test/java/org/sonar/core/purge/PurgeCommandsTest.java @@ -115,12 +115,10 @@ public class PurgeCommandsTest extends AbstractDaoTestCase { @Test public void shouldDeleteResource() { setupData("shouldDeleteResource"); - SqlSession session = getMyBatis().openSession(); - try { + try (SqlSession session = getMyBatis().openSession()) { new PurgeCommands(session, profiler).deleteResources(newArrayList(new IdUuidPair(1L, "1"))); - } finally { - MyBatis.closeQuietly(session); } + assertEmptyTables("projects", "snapshots", "events", "issues", "issue_changes", "authors"); } diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteResource.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteResource.xml index e7d8f49a2b4..2f30df757d0 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteResource.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteResource.xml @@ -16,7 +16,7 @@ version="[null]" path="[null]"/> <events id="1" name="Version 1.0" resource_id="1" snapshot_id="1" category="VERSION" description="[null]" - event_date="2008-12-02 13:58:00.00" created_at="[null]" event_data="[null]"/> + event_date="1228222680000" created_at="1228222680000" event_data="[null]"/> <issues id="1" kee="ABCDE" component_uuid="1" project_uuid="1" status="CLOSED" resolution="[null]" line="200" severity="BLOCKER" diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteSnapshot-result.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteSnapshot-result.xml index 3bac29d413a..f7a0a3a81b3 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteSnapshot-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteSnapshot-result.xml @@ -26,7 +26,7 @@ parent_dependency_id="[null]" project_snapshot_id="1" dep_usage="USES" dep_weight="1" from_scope="PRJ" to_scope="LIB"/> <events id="1" name="Version 1.0" resource_id="1" snapshot_id="1" category="VERSION" description="[null]" - event_date="2008-12-02 13:58:00.00" created_at="[null]" event_data="[null]"/> + event_date="1228222680000" created_at="1228222680000" event_data="[null]"/> <duplications_index id="1" project_snapshot_id="1" snapshot_id="1" hash="bb" index_in_file="0" start_line="0" end_line="0"/> </dataset> diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteSnapshot.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteSnapshot.xml index 8fd273d1c08..45eaee58163 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteSnapshot.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteSnapshot.xml @@ -25,7 +25,7 @@ parent_dependency_id="[null]" project_snapshot_id="1" dep_usage="USES" dep_weight="1" from_scope="PRJ" to_scope="LIB"/> <events id="1" name="Version 1.0" resource_id="1" snapshot_id="1" category="VERSION" description="[null]" - event_date="2008-12-02 13:58:00.00" created_at="[null]" event_data="[null]"/> + event_date="1228222680000" created_at="1228222680000" event_data="[null]"/> <duplications_index id="1" project_snapshot_id="1" snapshot_id="1" hash="bb" index_in_file="0" start_line="0" end_line="0"/> @@ -58,7 +58,7 @@ parent_dependency_id="[null]" project_snapshot_id="5" dep_usage="USES" dep_weight="1" from_scope="PRJ" to_scope="LIB"/> <events id="2" name="Version 1.0" resource_id="5" snapshot_id="5" category="VERSION" description="[null]" - event_date="2008-12-02 13:58:00.00" created_at="[null]" event_data="[null]"/> + event_date="1228222680000" created_at="1228222680000" event_data="[null]"/> <duplications_index id="2" project_snapshot_id="5" snapshot_id="5" hash="bb" index_in_file="0" start_line="0" end_line="0"/> diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldPurgeSnapshot-result.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldPurgeSnapshot-result.xml index bcd5544b9f6..4a45a7fc405 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldPurgeSnapshot-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldPurgeSnapshot-result.xml @@ -42,8 +42,8 @@ Note that measures, events and reviews are not deleted. <!--dep_usage="USES" dep_weight="1" from_scope="LIB" to_scope="PRJ"/>--> <events id="1" resource_id="1" snapshot_id="1" - category="VERSION" description="[null]" name="Version 1.0" event_date="2008-12-02 13:58:00.00" - created_at="[null]" + category="VERSION" description="[null]" name="Version 1.0" event_date="1228222680000" + created_at="1228222680000" event_data="[null]"/> <!--<duplications_index id="1" project_snapshot_id="1" snapshot_id="1"--> @@ -81,8 +81,8 @@ Note that measures, events and reviews are not deleted. dep_usage="USES" dep_weight="1" from_scope="LIB" to_scope="PRJ"/> <events id="2" resource_id="2" snapshot_id="2" - category="VERSION" description="[null]" name="Version 1.0" event_date="2008-12-02 13:58:00.00" - created_at="[null]" + category="VERSION" description="[null]" name="Version 1.0" event_date="1228222680000" + created_at="1228222680000" event_data="[null]"/> <duplications_index id="2" project_snapshot_id="2" snapshot_id="2" diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldPurgeSnapshot.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldPurgeSnapshot.xml index 5a576b3edf8..6c6e5bd44c2 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldPurgeSnapshot.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldPurgeSnapshot.xml @@ -27,8 +27,8 @@ dep_usage="USES" dep_weight="1" from_scope="LIB" to_scope="PRJ"/> <events id="1" resource_id="1" snapshot_id="1" - category="VERSION" description="[null]" name="Version 1.0" event_date="2008-12-02 13:58:00.00" - created_at="[null]" + category="VERSION" description="[null]" name="Version 1.0" event_date="1228222680000" + created_at="1228222680000" event_data="[null]"/> <duplications_index id="1" project_snapshot_id="1" snapshot_id="1" @@ -65,8 +65,8 @@ dep_usage="USES" dep_weight="1" from_scope="LIB" to_scope="PRJ"/> <events id="2" resource_id="2" snapshot_id="2" - category="VERSION" description="[null]" name="Version 1.0" event_date="2008-12-02 13:58:00.00" - created_at="[null]" + category="VERSION" description="[null]" name="Version 1.0" event_date="1228222680000" + created_at="1228222680000" event_data="[null]"/> <duplications_index id="2" project_snapshot_id="2" snapshot_id="2" diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldSelectPurgeableSnapshots.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldSelectPurgeableSnapshots.xml index f3a3b9bd0b3..78e14ead2a7 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldSelectPurgeableSnapshots.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldSelectPurgeableSnapshots.xml @@ -56,7 +56,7 @@ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000" version="[null]" path="[null]"/> <events id="2" resource_id="1" snapshot_id="5" - category="Version" description="[null]" name="Version 1.0" event_date="2008-12-02 13:58:00.00" created_at="[null]" + category="Version" description="[null]" name="Version 1.0" event_date="1228222680000" created_at="1228222680000" event_data="[null]"/> </dataset> diff --git a/sonar-graph/src/main/java/org/sonar/graph/Dsm.java b/sonar-graph/src/main/java/org/sonar/graph/Dsm.java index 4d8673d1992..811cad56e3e 100644 --- a/sonar-graph/src/main/java/org/sonar/graph/Dsm.java +++ b/sonar-graph/src/main/java/org/sonar/graph/Dsm.java @@ -31,7 +31,6 @@ public class Dsm<V> { private final DsmCell[][] cells; private final int dimension; private final DirectedGraphAccessor<V, ? extends Edge<V>> graph; - private boolean atLeastOneDependency = false; public Dsm(DirectedGraphAccessor<V, ? extends Edge<V>> graph, Collection<V> vertices, Set<Edge> feedbackEdges) { this.graph = graph; @@ -57,7 +56,6 @@ public class Dsm<V> { Edge<V> edge = graph.getEdge(from, to); if (edge != null) { - atLeastOneDependency = true; boolean isFeedbackEdge = feedbackEdges.contains(edge); result[x][y] = new DsmCell(edge, isFeedbackEdge); } @@ -171,13 +169,6 @@ public class Dsm<V> { return cells[x][y]; } - /** - * @since 5.0 - */ - public boolean hasAtLeastOneDependency() { - return atLeastOneDependency; - } - public V[] getVertices() { V[] verticesCopy = (V[]) new Object[vertices.length]; System.arraycopy(vertices, 0, verticesCopy, 0, vertices.length); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java index 8c802c2ca98..eb4db71fe41 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/Event.java @@ -26,6 +26,9 @@ import org.sonar.api.database.model.Snapshot; import javax.persistence.*; import java.util.Date; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.sonar.api.utils.DateUtils.longToDate; + /** * @since 1.10 */ @@ -46,10 +49,10 @@ public class Event extends BaseIdentifiable { private String category; @Column(name = "event_date", updatable = true, nullable = false) - private Date date; + private Long date; - @Column(name = "created_at", updatable = true, nullable = true) - private Date createdAt; + @Column(name = "created_at", updatable = true, nullable = false) + private Long createdAt; @Column(name = "event_data", updatable = true, nullable = true) private String data; @@ -103,11 +106,11 @@ public class Event extends BaseIdentifiable { } public Date getDate() { - return date; + return longToDate(date); } public void setDate(Date date) { - this.date = date; + this.date = date.getTime(); } public Snapshot getSnapshot() { @@ -115,19 +118,17 @@ public class Event extends BaseIdentifiable { } public Date getCreatedAt() { - return createdAt; + return new Date(createdAt); } public void setCreatedAt(Date createdAt) { - this.createdAt = createdAt; + this.createdAt = createdAt.getTime(); } public final void setSnapshot(Snapshot snapshot) { - this.snapshot = snapshot; - if (snapshot != null) { - this.date = (snapshot.getCreatedAtMs() == null ? null : new Date(snapshot.getCreatedAtMs())); - this.resourceId = snapshot.getResourceId(); - } + this.snapshot = checkNotNull(snapshot, "it is not possible to set a null snapshot linked to an event"); + this.date = snapshot.getCreatedAtMs(); + this.resourceId = snapshot.getResourceId(); } public Integer getResourceId() { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/SensorContext.java index b75209e1538..6a5463e67f9 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/SensorContext.java @@ -30,6 +30,7 @@ import org.sonar.api.resources.Resource; import org.sonar.api.rules.Violation; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import java.io.Serializable; import java.util.Collection; @@ -191,14 +192,23 @@ public interface SensorContext extends org.sonar.api.batch.sensor.SensorContext // ----------- DEPENDENCIES BETWEEN RESOURCES -------------- /** - * Build a new dependency : from depends upon to. The dependency is NOT saved. The method saveDependency() must still be executed. + * @deprecated since 5.1 use {@link #newDependency()} */ Dependency saveDependency(Dependency dependency); + /** + * @deprecated since 5.1 Sensors should not read but only save data + */ Set<Dependency> getDependencies(); + /** + * @deprecated since 5.1 Sensors should not read but only save data + */ Collection<Dependency> getIncomingDependencies(Resource to); + /** + * @deprecated since 5.1 Sensors should not read but only save data + */ Collection<Dependency> getOutgoingDependencies(Resource from); // ----------- FILE SOURCES -------------- @@ -242,7 +252,7 @@ public interface SensorContext extends org.sonar.api.batch.sensor.SensorContext * @param date the event date * @return the created event */ - Event createEvent(Resource resource, String name, String description, String category, Date date); + Event createEvent(Resource resource, String name, @Nullable String description, String category, @Nullable Date date); /** * Deletes an event diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/SonarIndex.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/SonarIndex.java index f21b83bab5f..6714ed9c1bb 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/SonarIndex.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/SonarIndex.java @@ -30,6 +30,7 @@ import org.sonar.api.rules.Violation; import org.sonar.graph.DirectedGraphAccessor; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Date; @@ -155,7 +156,7 @@ public abstract class SonarIndex implements DirectedGraphAccessor<Resource, Depe public abstract void deleteEvent(Event event); - public abstract Event addEvent(Resource resource, String name, String description, String category, Date date); + public abstract Event addEvent(Resource resource, String name, String description, String category, @Nullable Date date); public final Collection<Dependency> getOutgoingDependencies(Resource from) { return getOutgoingEdges(from); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java index b36786c2333..a1de94d8b3f 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java @@ -24,7 +24,7 @@ import org.sonar.api.batch.CpdMapping; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.rule.ActiveRules; -import org.sonar.api.batch.sensor.dependency.Dependency; +import org.sonar.api.batch.sensor.dependency.NewDependency; import org.sonar.api.batch.sensor.duplication.NewDuplication; import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; @@ -82,7 +82,6 @@ public interface SensorContext { /** * Builder to define highlighting of a file. - * @since 4.5 */ HighlightingBuilder highlightingBuilder(InputFile inputFile); @@ -90,7 +89,6 @@ public interface SensorContext { /** * Builder to define symbol references in a file. - * @since 4.5 */ SymbolTableBuilder symbolTableBuilder(InputFile inputFile); @@ -99,7 +97,6 @@ public interface SensorContext { /** * Builder to manually register duplication in a file. This can be used in addition to {@link CpdMapping} extension point. * Don't forget to call {@link NewDuplication#save()}. - * @since 5.1 */ NewDuplication newDuplication(); @@ -108,21 +105,18 @@ public interface SensorContext { /** * Create a new coverage report. * Don't forget to call {@link Coverage#save()} once all parameters are provided. - * @since 5.0 */ Coverage newCoverage(); /** * Create a new test case execution report. * Don't forget to call {@link TestCaseExecution#save()} once all parameters are provided. - * @since 5.0 */ TestCaseExecution newTestCaseExecution(); /** * Create a new test case coverage report. * Don't forget to call {@link TestCaseCoverage#save()} once all parameters are provided. - * @since 5.0 */ TestCaseCoverage newTestCaseCoverage(); @@ -130,9 +124,8 @@ public interface SensorContext { /** * Create a new dependency. - * Don't forget to call {@link Dependency#save()} once all parameters are provided. - * @since 5.0 + * Don't forget to call {@link NewDependency#save()} once all parameters are provided. */ - Dependency newDependency(); + NewDependency newDependency(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/Dependency.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/Dependency.java index 87bd3029fbd..56185acaf3a 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/Dependency.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/Dependency.java @@ -19,36 +19,19 @@ */ package org.sonar.api.batch.sensor.dependency; -import com.google.common.annotations.Beta; -import org.sonar.api.batch.fs.InputFile; /** - * @since 5.0 + * @since 5.1 */ -@Beta public interface Dependency { - InputFile from(); + String fromKey(); - Dependency from(InputFile from); - - InputFile to(); - - Dependency to(InputFile to); + String toKey(); /** * Default weight value is 1. */ int weight(); - /** - * Set the weight of the dependency. - */ - Dependency weight(int weight); - - /** - * Save the dependency. - */ - void save(); - } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/NewDependency.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/NewDependency.java new file mode 100644 index 00000000000..8d62a2f6516 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/NewDependency.java @@ -0,0 +1,45 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.sensor.dependency; + +import org.sonar.api.batch.fs.InputFile; + +/** + * Builder to create new Dependency. + * Should not be implemented by client. + * @since 5.1 + */ +public interface NewDependency { + + NewDependency from(InputFile from); + + NewDependency to(InputFile to); + + /** + * Set the weight of the dependency. If not set default weight is 1. + */ + NewDependency weight(int weight); + + /** + * Save the dependency. It is not permitted so save several time a dependency between two same files. + */ + void save(); + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/internal/DefaultDependency.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/internal/DefaultDependency.java index c7ab2fcc997..9dcb3d34ece 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/internal/DefaultDependency.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/internal/DefaultDependency.java @@ -23,16 +23,18 @@ import com.google.common.base.Preconditions; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.sensor.SensorStorage; import org.sonar.api.batch.sensor.dependency.Dependency; +import org.sonar.api.batch.sensor.dependency.NewDependency; import org.sonar.api.batch.sensor.internal.DefaultStorable; import javax.annotation.Nullable; -public class DefaultDependency extends DefaultStorable implements Dependency { +public class DefaultDependency extends DefaultStorable implements Dependency, NewDependency { - private InputFile from; - private InputFile to; + private String fromKey; + private String toKey; private int weight = 1; public DefaultDependency() { @@ -44,21 +46,21 @@ public class DefaultDependency extends DefaultStorable implements Dependency { } @Override - public Dependency from(InputFile from) { + public DefaultDependency from(InputFile from) { Preconditions.checkNotNull(from, "InputFile should be non null"); - this.from = from; + this.fromKey = ((DefaultInputFile) from).key(); return this; } @Override - public Dependency to(InputFile to) { + public DefaultDependency to(InputFile to) { Preconditions.checkNotNull(to, "InputFile should be non null"); - this.to = to; + this.toKey = ((DefaultInputFile) to).key(); return this; } @Override - public Dependency weight(int weight) { + public DefaultDependency weight(int weight) { Preconditions.checkArgument(weight > 1, "weight should be greater than 1"); this.weight = weight; return this; @@ -66,20 +68,30 @@ public class DefaultDependency extends DefaultStorable implements Dependency { @Override public void doSave() { - Preconditions.checkState(!this.from.equals(this.to), "From and To can't be the same inputFile"); - Preconditions.checkNotNull(this.from, "From inputFile can't be null"); - Preconditions.checkNotNull(this.to, "To inputFile can't be null"); + Preconditions.checkState(!this.fromKey.equals(this.toKey), "From and To can't be the same inputFile"); + Preconditions.checkNotNull(this.fromKey, "From inputFile can't be null"); + Preconditions.checkNotNull(this.toKey, "To inputFile can't be null"); storage.store((Dependency) this); } @Override - public InputFile from() { - return this.from; + public String fromKey() { + return this.fromKey; + } + + public DefaultDependency setFromKey(String fromKey) { + this.fromKey = fromKey; + return this; } @Override - public InputFile to() { - return this.to; + public String toKey() { + return this.toKey; + } + + public DefaultDependency setToKey(String toKey) { + this.toKey = toKey; + return this; } @Override @@ -87,6 +99,11 @@ public class DefaultDependency extends DefaultStorable implements Dependency { return this.weight; } + public DefaultDependency setWeight(int weight) { + this.weight = weight; + return this; + } + // For testing purpose @Override @@ -102,8 +119,8 @@ public class DefaultDependency extends DefaultStorable implements Dependency { } DefaultDependency rhs = (DefaultDependency) obj; return new EqualsBuilder() - .append(from, rhs.from) - .append(to, rhs.to) + .append(fromKey, rhs.fromKey) + .append(toKey, rhs.toKey) .append(weight, rhs.weight) .isEquals(); } @@ -111,8 +128,8 @@ public class DefaultDependency extends DefaultStorable implements Dependency { @Override public int hashCode() { return new HashCodeBuilder(27, 45). - append(from). - append(to). + append(fromKey). + append(toKey). append(weight). toHashCode(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/License.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/License.java index 0e0f316d532..d0f53d44024 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/config/License.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/License.java @@ -102,7 +102,15 @@ public final class License { @VisibleForTesting boolean isExpired(Date now) { Date date = getExpirationDate(); - return date != null && !date.after(org.apache.commons.lang.time.DateUtils.truncate(now, Calendar.DATE)); + if (date == null) { + return false; + } + // SONAR-6079 include last day + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.DAY_OF_MONTH, 1); + cal.add(Calendar.SECOND, -1); + return now.after(cal.getTime()); } @Nullable diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/design/Dependency.java b/sonar-plugin-api/src/main/java/org/sonar/api/design/Dependency.java index 5b7537f20da..bc965583881 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/design/Dependency.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/design/Dependency.java @@ -50,11 +50,25 @@ public class Dependency implements Edge<Resource> { return from; } + /** + * For internal use only + */ + public void setFrom(Resource from) { + this.from = from; + } + @Override public Resource getTo() { return to; } + /** + * For internal use only + */ + public void setTo(Resource to) { + this.to = to; + } + public String getUsage() { return usage; } @@ -105,26 +119,26 @@ public class Dependency implements Edge<Resource> { } Dependency other = (Dependency) obj; return new EqualsBuilder() - .append(from, other.from) - .append(to, other.to) - .isEquals(); + .append(from, other.from) + .append(to, other.to) + .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37) - .append(from) - .append(to) - .toHashCode(); + .append(from) + .append(to) + .toHashCode(); } @Override public String toString() { return new ToStringBuilder(this) - .append("from", from) - .append("to", to) - .append("weight", weight) - .append("usage", usage) - .toString(); + .append("from", from) + .append("to", to) + .append("weight", weight) + .append("usage", usage) + .toString(); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceUtils.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceUtils.java index 19ce2aa8fc8..80d273a13ed 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceUtils.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceUtils.java @@ -120,8 +120,17 @@ public final class ResourceUtils { /** * @return whether a resource is a unit test class + * @deprecated since 5.1 use {@link #isUnitTestFile(Resource)} */ + @Deprecated public static boolean isUnitTestClass(Resource resource) { + return isUnitTestFile(resource); + } + + /** + * @return whether a resource is a unit test class + */ + public static boolean isUnitTestFile(Resource resource) { return Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier()); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java index 4ddf32cd622..f26cad3ae4c 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java @@ -293,6 +293,61 @@ public interface RulesDefinition extends ServerExtension { */ public static final String UNIT_TESTABILITY = "UNIT_TESTABILITY"; + /** + * Related to characteristic ACCESSIBILITY + */ + public static final String USABILITY_ACCESSIBILITY = "USABILITY_ACCESSIBILITY"; + + /** + * Related to characteristic ACCESSIBILITY + */ + public static final String USABILITY_COMPLIANCE = "USABILITY_COMPLIANCE"; + + /** + * Related to characteristic ACCESSIBILITY + */ + public static final String USABILITY_EASE_OF_USE = "USABILITY_EASE_OF_USE"; + + /** + * Related to characteristic REUSABILITY + */ + public static final String REUSABILITY_COMPLIANCE = "REUSABILITY_COMPLIANCE"; + + /** + * Related to characteristic PORTABILITY + */ + public static final String PORTABILITY_COMPLIANCE = "PORTABILITY_COMPLIANCE"; + + /** + * Related to characteristic MAINTAINABILITY + */ + public static final String MAINTAINABILITY_COMPLIANCE = "MAINTAINABILITY_COMPLIANCE"; + + /** + * Related to characteristic SECURITY + */ + public static final String SECURITY_COMPLIANCE = "SECURITY_COMPLIANCE"; + + /** + * Related to characteristic EFFICIENCY + */ + public static final String EFFICIENCY_COMPLIANCE = "EFFICIENCY_COMPLIANCE"; + + /** + * Related to characteristic CHANGEABILITY + */ + public static final String CHANGEABILITY_COMPLIANCE = "CHANGEABILITY_COMPLIANCE"; + + /** + * Related to characteristic RELIABILITY + */ + public static final String RELIABILITY_COMPLIANCE = "RELIABILITY_COMPLIANCE"; + + /** + * Related to characteristic TESTABILITY + */ + public static final String TESTABILITY_COMPLIANCE = "TESTABILITY_COMPLIANCE"; + private SubCharacteristics() { // only constants } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java index 305132e5251..ed490dd9ee7 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java @@ -23,6 +23,9 @@ import org.apache.commons.codec.binary.Base64; import org.junit.Test; import org.sonar.api.utils.DateUtils; +import java.util.Calendar; +import java.util.TimeZone; + import static org.assertj.core.api.Assertions.assertThat; public class LicenseTest { @@ -131,10 +134,20 @@ public class LicenseTest { public void isExpired() { License license = License.readPlainText(V2_FORMAT); - assertThat(license.isExpired(DateUtils.parseDate("2013-06-23"))).isTrue(); - assertThat(license.isExpired(DateUtils.parseDate("2012-05-18"))).isTrue(); - assertThat(license.isExpired(DateUtils.parseDateTime("2012-05-18T15:50:45+0100"))).isTrue(); assertThat(license.isExpired(DateUtils.parseDate("2011-01-01"))).isFalse(); + Calendar sameDay = Calendar.getInstance(TimeZone.getDefault()); + sameDay.setTime(DateUtils.parseDate("2012-05-18")); + assertThat(license.isExpired(sameDay.getTime())).isFalse(); + sameDay.set(Calendar.HOUR_OF_DAY, 15); + assertThat(license.isExpired(sameDay.getTime())).isFalse(); + sameDay.set(Calendar.HOUR_OF_DAY, 23); + sameDay.set(Calendar.MINUTE, 59); + sameDay.set(Calendar.SECOND, 59); + assertThat(license.isExpired(sameDay.getTime())).isFalse(); + // The day after + sameDay.add(Calendar.SECOND, 1); + assertThat(license.isExpired(sameDay.getTime())).isTrue(); + assertThat(license.isExpired(DateUtils.parseDate("2013-06-23"))).isTrue(); } @Test |