aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorStephane Gamard <stephane.gamard@searchbox.com>2014-07-25 15:45:59 +0200
committerStephane Gamard <stephane.gamard@searchbox.com>2014-07-25 15:45:59 +0200
commitc799e9745bb3f33e7146b870853ede4eb05a8a1f (patch)
treeaae7295086e4d37937ac70686d7beaa3293febd3 /server
parent83d4e04cfd57d992e0f67da2cfe06260e413408f (diff)
parent5de168c51c20ef298d1257cc91517eeca9db7d9c (diff)
downloadsonarqube-c799e9745bb3f33e7146b870853ede4eb05a8a1f.tar.gz
sonarqube-c799e9745bb3f33e7146b870853ede4eb05a8a1f.zip
Merge branch 'master' into SONAR-4898
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java41
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java40
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java43
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java44
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java52
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java95
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java112
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_active_rules.json25
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json14
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json18
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json17
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json17
-rw-r--r--server/sonar-web/Gruntfile.coffee18
-rw-r--r--server/sonar-web/pom.xml1
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/app.coffee1
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/header.coffee14
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee2
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee12
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee3
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee12
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee9
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/main.coffee7
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee3
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee8
-rw-r--r--server/sonar-web/src/main/coffee/component-viewer/mockjax.coffee27
-rw-r--r--server/sonar-web/src/main/coffee/dashboard/file-app.coffee1
-rw-r--r--server/sonar-web/src/main/coffee/drilldown/app.coffee1
-rw-r--r--server/sonar-web/src/main/coffee/drilldown/conf.coffee28
-rw-r--r--server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs25
-rw-r--r--server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs2
-rw-r--r--server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs14
-rw-r--r--server/sonar-web/src/main/js/navigator/filters/choice-filters.js2
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/lib.js23
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js29
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json13
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js455
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/cross-project-duplications.json33
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/duplications-in-deleted-files.json24
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources.json154
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/test-cases.json235
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/app.json4
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/covered-files.json7
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/resources.json25
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/source.json24
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/tests.json14
-rw-r--r--server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade3
-rw-r--r--server/sonar-web/src/main/less/component-viewer.less1
-rw-r--r--server/sonar-web/src/main/less/style.less2
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb4
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb96
54 files changed, 1617 insertions, 247 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java
index 8b4a9f23054..ba9163afecd 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java
@@ -28,7 +28,6 @@ import org.sonar.api.server.ws.RequestHandler;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.batch.protocol.input.ProjectReferentials;
-import org.sonar.batch.protocol.input.QProfile;
import org.sonar.core.UtcDateUtils;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.permission.GlobalPermissions;
@@ -39,7 +38,11 @@ import org.sonar.core.properties.PropertyDto;
import org.sonar.core.qualityprofile.db.QualityProfileDto;
import org.sonar.server.db.DbClient;
import org.sonar.server.plugins.MimeTypes;
+import org.sonar.server.qualityprofile.ActiveRule;
import org.sonar.server.qualityprofile.QProfileFactory;
+import org.sonar.server.qualityprofile.QProfileLoader;
+import org.sonar.server.rule.Rule;
+import org.sonar.server.rule.RuleService;
import org.sonar.server.user.UserSession;
import java.util.List;
@@ -54,12 +57,17 @@ public class ProjectReferentialsAction implements RequestHandler {
private final DbClient dbClient;
private final PropertiesDao propertiesDao;
private final QProfileFactory qProfileFactory;
+ private final QProfileLoader qProfileLoader;
+ private final RuleService ruleService;
private final Languages languages;
- public ProjectReferentialsAction(DbClient dbClient, PropertiesDao propertiesDao, QProfileFactory qProfileFactory, Languages languages) {
+ public ProjectReferentialsAction(DbClient dbClient, PropertiesDao propertiesDao, QProfileFactory qProfileFactory, QProfileLoader qProfileLoader,
+ RuleService ruleService, Languages languages) {
this.dbClient = dbClient;
this.propertiesDao = propertiesDao;
this.qProfileFactory = qProfileFactory;
+ this.qProfileLoader = qProfileLoader;
+ this.ruleService = ruleService;
this.languages = languages;
}
@@ -88,6 +96,7 @@ public class ProjectReferentialsAction implements RequestHandler {
String projectKey = request.mandatoryParam(PARAM_KEY);
addSettings(ref, projectKey, hasScanPerm, session);
addProfiles(ref, projectKey, session);
+ addActiveRules(ref);
response.stream().setMediaType(MimeTypes.JSON);
IOUtils.write(ref.toJson(), response.stream().output());
@@ -127,9 +136,31 @@ public class ProjectReferentialsAction implements RequestHandler {
QualityProfileDto qualityProfileDto = qProfileFactory.getByProjectAndLanguage(session, projectKey, languageKey);
qualityProfileDto = qualityProfileDto != null ? qualityProfileDto : qProfileFactory.getDefault(session, languageKey);
if (qualityProfileDto != null) {
- QProfile profile = new QProfile(qualityProfileDto.getKey(), qualityProfileDto.getName(), qualityProfileDto.getLanguage(),
- UtcDateUtils.parseDateTime(qualityProfileDto.getRulesUpdatedAt()));
- ref.addQProfile(profile);
+ ref.addQProfile(new org.sonar.batch.protocol.input.QProfile(
+ qualityProfileDto.getKey(),
+ qualityProfileDto.getName(),
+ qualityProfileDto.getLanguage(),
+ UtcDateUtils.parseDateTime(qualityProfileDto.getRulesUpdatedAt())));
+ }
+ }
+ }
+
+ private void addActiveRules(ProjectReferentials ref) {
+ for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) {
+ for (ActiveRule activeRule : qProfileLoader.findActiveRulesByProfile(qProfile.key())) {
+ Rule rule = ruleService.getByKey(activeRule.key().ruleKey());
+ org.sonar.batch.protocol.input.ActiveRule inputActiveRule = new org.sonar.batch.protocol.input.ActiveRule(
+ activeRule.key().ruleKey().repository(),
+ activeRule.key().ruleKey().rule(),
+ rule.name(),
+ activeRule.severity(),
+ rule.internalKey(),
+ qProfile.language()
+ );
+ for (Map.Entry<String, String> entry : activeRule.params().entrySet()) {
+ inputActiveRule.addParam(entry.getKey(), entry.getValue());
+ }
+ ref.addActiveRule(inputActiveRule);
}
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java
index fecade74cd9..474d7f42f47 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java
@@ -58,7 +58,7 @@ public class RegisterQualityGates implements Startable {
private void createBuiltinQualityGate() {
QualityGateDto builtin = qualityGates.create(BUILTIN_QUALITY_GATE);
qualityGates.createCondition(builtin.getId(), CoreMetrics.BLOCKER_VIOLATIONS_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", null);
- qualityGates.createCondition(builtin.getId(), CoreMetrics.CRITICAL_VIOLATIONS_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", null);
+ qualityGates.createCondition(builtin.getId(), CoreMetrics.CRITICAL_VIOLATIONS_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", 3);
qualityGates.createCondition(builtin.getId(), CoreMetrics.TEST_ERRORS_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", null);
qualityGates.createCondition(builtin.getId(), CoreMetrics.TEST_FAILURES_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", null);
qualityGates.createCondition(builtin.getId(), CoreMetrics.NEW_COVERAGE_KEY, QualityGateConditionDto.OPERATOR_LESS_THAN, null, "80", 3);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java
index 0e7b1042e12..03af506f7b5 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java
@@ -63,7 +63,7 @@ public class QProfileFactory implements ServerComponent {
return profile;
}
- QualityProfileDto create(DbSession dbSession, QProfileName name) {
+ public QualityProfileDto create(DbSession dbSession, QProfileName name) {
QualityProfileDto dto = db.qualityProfileDao().getByNameAndLanguage(name.getName(), name.getLanguage(), dbSession);
if (dto != null) {
throw new BadRequestException("Quality profile already exists: " + name);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java
index 6ce3e100b32..ae6bf7a2154 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java
@@ -25,9 +25,15 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.unit.TimeValue;
-import org.elasticsearch.index.query.*;
+import org.elasticsearch.index.query.BoolFilterBuilder;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.FilterBuilder;
+import org.elasticsearch.index.query.FilterBuilders;
+import org.elasticsearch.index.query.MatchQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.query.SimpleQueryStringBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
@@ -43,11 +49,22 @@ import org.sonar.core.profiling.StopWatch;
import org.sonar.core.rule.RuleDto;
import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer;
import org.sonar.server.rule.Rule;
-import org.sonar.server.search.*;
+import org.sonar.server.search.BaseIndex;
+import org.sonar.server.search.ESNode;
+import org.sonar.server.search.IndexDefinition;
+import org.sonar.server.search.IndexField;
+import org.sonar.server.search.QueryOptions;
+import org.sonar.server.search.Result;
import javax.annotation.CheckForNull;
import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static com.google.common.collect.Lists.newArrayList;
@@ -166,16 +183,11 @@ public class RuleIndex extends BaseIndex<Rule, RuleDto, RuleKey> {
String queryString = query.getQueryText();
// Human readable type of querying
- qb.should(QueryBuilders.queryString(query.getQueryText())
- .field(RuleNormalizer.RuleField.NAME.field() + "." + IndexField.SEARCH_WORDS_SUFFIX, 20f)
- .field(RuleNormalizer.RuleField.HTML_DESCRIPTION.field() + "." + IndexField.SEARCH_WORDS_SUFFIX, 3f)
- .enablePositionIncrements(true)
- .defaultOperator(QueryStringQueryBuilder.Operator.AND)
- .fuzziness(Fuzziness.ONE)
- .autoGeneratePhraseQueries(true)
- .lenient(false)
- .useDisMax(true)
- .boost(20f));
+ qb.should(QueryBuilders.simpleQueryString(query.getQueryText())
+ .field(RuleNormalizer.RuleField.NAME.field() + "." + IndexField.SEARCH_WORDS_SUFFIX, 20f)
+ .field(RuleNormalizer.RuleField.HTML_DESCRIPTION.field() + "." + IndexField.SEARCH_WORDS_SUFFIX, 3f)
+ .defaultOperator(SimpleQueryStringBuilder.Operator.AND)
+ ).boost(20f);
// Match and partial Match queries
qb.should(this.termQuery(RuleNormalizer.RuleField.KEY, queryString, 15f));
diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java
index b118a340140..dba6cd180a7 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java
@@ -105,23 +105,44 @@ public class RuleMapping extends BaseMapping<RuleDoc, RuleMappingContext> {
private void mapDebtFields() {
map("defaultDebtChar", new IndexStringMapper("defaultDebtChar", RuleNormalizer.RuleField.DEFAULT_CHARACTERISTIC.field()));
map("defaultDebtSubChar", new IndexStringMapper("defaultDebtSubChar", RuleNormalizer.RuleField.DEFAULT_SUB_CHARACTERISTIC.field()));
+
map("debtChar", new IndexStringMapper("debtChar", RuleNormalizer.RuleField.CHARACTERISTIC.field(),
RuleNormalizer.RuleField.DEFAULT_CHARACTERISTIC.field()));
map("debtSubChar", new IndexStringMapper("debtSubChar", RuleNormalizer.RuleField.SUB_CHARACTERISTIC.field(),
RuleNormalizer.RuleField.DEFAULT_SUB_CHARACTERISTIC.field()));
+
map("debtCharName", new CharacteristicNameMapper());
map("debtSubCharName", new SubCharacteristicNameMapper());
- map("debtRemFn", new IndexStringMapper("debtRemFnType", RuleNormalizer.RuleField.DEBT_FUNCTION_TYPE.field(),
- RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_TYPE.field()));
- map("debtRemFn", new IndexStringMapper("debtRemFnCoeff", RuleNormalizer.RuleField.DEBT_FUNCTION_COEFFICIENT.field(),
- RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_COEFFICIENT.field()));
- map("debtRemFn", new IndexStringMapper("debtRemFnOffset", RuleNormalizer.RuleField.DEBT_FUNCTION_OFFSET.field(),
- RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_OFFSET.field()));
+
map("defaultDebtRemFn", new IndexStringMapper("defaultDebtRemFnType", RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_TYPE.field()));
map("defaultDebtRemFn", new IndexStringMapper("defaultDebtRemFnCoeff", RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_COEFFICIENT.field()));
map("defaultDebtRemFn", new IndexStringMapper("defaultDebtRemFnOffset", RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_OFFSET.field()));
map("effortToFixDescription", RuleNormalizer.RuleField.FIX_DESCRIPTION.field());
map("debtOverloaded", new OverriddenMapper());
+
+ map("debtRemFn", new EffectiveDebtRemFn("debtRemFnType", RuleNormalizer.RuleField.DEBT_FUNCTION_TYPE.field(),
+ RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_TYPE.field()));
+ map("debtRemFn", new EffectiveDebtRemFn("debtRemFnCoeff", RuleNormalizer.RuleField.DEBT_FUNCTION_COEFFICIENT.field(),
+ RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_COEFFICIENT.field()));
+ map("debtRemFn", new EffectiveDebtRemFn("debtRemFnOffset", RuleNormalizer.RuleField.DEBT_FUNCTION_OFFSET.field(),
+ RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_OFFSET.field()));
+ }
+
+ public static class EffectiveDebtRemFn extends IndexStringMapper<RuleDoc,RuleMappingContext> {
+
+ public EffectiveDebtRemFn(String key, String indexKey, String defaultIndexKey) {
+ super(key, indexKey, defaultIndexKey);
+ }
+
+ @Override
+ public void write(JsonWriter json, RuleDoc doc, RuleMappingContext context) {
+ if(doc.debtOverloaded()){
+ Object val = doc.getNullableField(indexFields[0]);
+ json.prop(key, val != null ? val.toString() : null);
+ } else {
+ super.write(json,doc,context);
+ }
+ }
}
private void mapParamFields() {
@@ -144,14 +165,12 @@ public class RuleMapping extends BaseMapping<RuleDoc, RuleMappingContext> {
public void write(Rule rule, JsonWriter json, @Nullable SearchOptions options) {
RuleMappingContext context = new RuleMappingContext();
- String characteristicKey;
- if (needDebtCharacteristicNames(options) && (characteristicKey = rule.debtCharacteristicKey()) != null) {
+ if (needDebtCharacteristicNames(options) && rule.debtCharacteristicKey() != null) {
// load debt characteristics if requested
- context.add(debtModel.characteristicByKey(characteristicKey));
+ context.add(debtModel.characteristicByKey(rule.debtCharacteristicKey()));
}
- String subCharacteristicKey;
- if (needDebtSubCharacteristicNames(options) && (subCharacteristicKey = rule.debtSubCharacteristicKey()) != null) {
- context.add(debtModel.characteristicByKey(subCharacteristicKey));
+ if (needDebtSubCharacteristicNames(options) && rule.debtSubCharacteristicKey() != null) {
+ context.add(debtModel.characteristicByKey(rule.debtSubCharacteristicKey()));
}
doWrite((RuleDoc) rule, context, json, options);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java b/server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java
index 69a73937c6a..8a4096a47a8 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java
@@ -133,7 +133,7 @@ public abstract class BaseMapping<DOC extends BaseDoc, CTX> implements ServerCom
* String field
*/
public static class IndexStringMapper<DOC extends BaseDoc, CTX> extends IndexMapper<DOC,CTX> {
- private final String key;
+ protected final String key;
public IndexStringMapper(String key, String indexKey, String defaultIndexKey) {
super(indexKey, defaultIndexKey);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java
index 50a7c340705..1a497bb014a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java
@@ -32,6 +32,8 @@ import org.sonar.api.resources.Languages;
import org.sonar.core.properties.PropertiesDao;
import org.sonar.server.db.DbClient;
import org.sonar.server.qualityprofile.QProfileFactory;
+import org.sonar.server.qualityprofile.QProfileLoader;
+import org.sonar.server.rule.RuleService;
import org.sonar.server.ws.WsTester;
import java.io.File;
@@ -59,7 +61,7 @@ public class BatchWsTest {
public void before() throws IOException {
tester = new WsTester(new BatchWs(batchIndex,
new GlobalReferentialsAction(mock(DbClient.class), mock(PropertiesDao.class)),
- new ProjectReferentialsAction(mock(DbClient.class), mock(PropertiesDao.class), mock(QProfileFactory.class), mock(Languages.class))));
+ new ProjectReferentialsAction(mock(DbClient.class), mock(PropertiesDao.class), mock(QProfileFactory.class), mock(QProfileLoader.class), mock(RuleService.class), mock(Languages.class))));
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java
index a04fcc58cf6..1c5a6aa7bb5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java
@@ -20,6 +20,7 @@
package org.sonar.server.batch;
+import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -27,15 +28,22 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.properties.PropertiesDao;
import org.sonar.core.properties.PropertyDto;
+import org.sonar.core.qualityprofile.db.ActiveRuleKey;
import org.sonar.core.qualityprofile.db.QualityProfileDto;
import org.sonar.server.component.persistence.ComponentDao;
import org.sonar.server.db.DbClient;
+import org.sonar.server.qualityprofile.ActiveRule;
import org.sonar.server.qualityprofile.QProfileFactory;
+import org.sonar.server.qualityprofile.QProfileLoader;
+import org.sonar.server.rule.Rule;
+import org.sonar.server.rule.RuleService;
import org.sonar.server.user.MockUserSession;
import org.sonar.server.ws.WsTester;
@@ -59,6 +67,12 @@ public class ProjectReferentialsActionTest {
QProfileFactory qProfileFactory;
@Mock
+ QProfileLoader qProfileLoader;
+
+ @Mock
+ RuleService ruleService;
+
+ @Mock
Languages languages;
@Mock
@@ -75,7 +89,8 @@ public class ProjectReferentialsActionTest {
when(language.getKey()).thenReturn("java");
when(languages.all()).thenReturn(new Language[]{language});
- tester = new WsTester(new BatchWs(mock(BatchIndex.class), mock(GlobalReferentialsAction.class), new ProjectReferentialsAction(dbClient, propertiesDao, qProfileFactory, languages)));
+ tester = new WsTester(new BatchWs(mock(BatchIndex.class), mock(GlobalReferentialsAction.class),
+ new ProjectReferentialsAction(dbClient, propertiesDao, qProfileFactory, qProfileLoader, ruleService, languages)));
}
@Test
@@ -126,7 +141,6 @@ public class ProjectReferentialsActionTest {
@Test
public void return_quality_profiles() throws Exception {
MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION);
-
String projectKey = "org.codehaus.sonar:sonar";
when(qProfileFactory.getByProjectAndLanguage(session, projectKey, "java")).thenReturn(
@@ -140,7 +154,6 @@ public class ProjectReferentialsActionTest {
@Test
public void return_quality_profile_from_default_profile() throws Exception {
MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION);
-
String projectKey = "org.codehaus.sonar:sonar";
when(qProfileFactory.getDefault(session, "java")).thenReturn(
@@ -151,4 +164,29 @@ public class ProjectReferentialsActionTest {
request.execute().assertJson(getClass(), "return_quality_profile_from_default_profile.json");
}
+ @Test
+ public void return_active_rules() throws Exception {
+ MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION);
+ String projectKey = "org.codehaus.sonar:sonar";
+
+ when(qProfileFactory.getByProjectAndLanguage(session, projectKey, "java")).thenReturn(
+ QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200")
+ );
+
+ RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
+ ActiveRule activeRule = mock(ActiveRule.class);
+ when(activeRule.key()).thenReturn(ActiveRuleKey.of("abcd", ruleKey));
+ when(activeRule.severity()).thenReturn(Severity.MINOR);
+ when(activeRule.params()).thenReturn(ImmutableMap.of("max", "2"));
+ when(qProfileLoader.findActiveRulesByProfile("abcd")).thenReturn(newArrayList(activeRule));
+
+ Rule rule = mock(Rule.class);
+ when(rule.name()).thenReturn("Avoid Cycle");
+ when(rule.internalKey()).thenReturn("squid-1");
+ when(ruleService.getByKey(ruleKey)).thenReturn(rule);
+
+ WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", projectKey);
+ request.execute().assertJson(getClass(), "return_active_rules.json");
+ }
+
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java
index 35eabc87859..18fb0f0f6f7 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java
@@ -244,7 +244,13 @@ public class RuleUpdaterMediumTest {
assertThat(indexedRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE);
assertThat(indexedRule.debtRemediationFunction().coefficient()).isNull();
assertThat(indexedRule.debtRemediationFunction().offset()).isEqualTo("1min");
+
assertThat(indexedRule.debtOverloaded()).isTrue();
+ assertThat(indexedRule.defaultDebtCharacteristicKey()).isEqualTo("RELIABILITY");
+ assertThat(indexedRule.defaultDebtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY");
+ assertThat(indexedRule.defaultDebtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
+ assertThat(indexedRule.defaultDebtRemediationFunction().coefficient()).isEqualTo("1d");
+ assertThat(indexedRule.defaultDebtRemediationFunction().offset()).isEqualTo("5min");
}
@Test
@@ -272,7 +278,47 @@ public class RuleUpdaterMediumTest {
assertThat(indexedRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
assertThat(indexedRule.debtRemediationFunction().coefficient()).isEqualTo("2d");
assertThat(indexedRule.debtRemediationFunction().offset()).isNull();
+
assertThat(indexedRule.debtOverloaded()).isTrue();
+ assertThat(indexedRule.defaultDebtCharacteristicKey()).isEqualTo("RELIABILITY");
+ assertThat(indexedRule.defaultDebtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY");
+ assertThat(indexedRule.defaultDebtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
+ assertThat(indexedRule.defaultDebtRemediationFunction().coefficient()).isEqualTo("1d");
+ assertThat(indexedRule.defaultDebtRemediationFunction().offset()).isNull();
+ }
+
+ @Test
+ public void override_debt_from_linear_with_offset_to_constant() throws Exception {
+ insertDebtCharacteristics(dbSession);
+ ruleDao.insert(dbSession, RuleTesting.newDto(RULE_KEY)
+ .setDefaultSubCharacteristicId(hardReliabilityId)
+ .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+ .setDefaultRemediationCoefficient("1d")
+ .setDefaultRemediationOffset("5min")
+ .setRemediationFunction(null)
+ .setRemediationCoefficient(null)
+ .setRemediationOffset(null));
+ dbSession.commit();
+
+ RuleUpdate update = RuleUpdate.createForPluginRule(RULE_KEY)
+ .setDebtRemediationFunction(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, null, "10min"));
+ updater.update(update, UserSession.get());
+ dbSession.clearCache();
+
+ // verify debt is overridden
+ Rule indexedRule = tester.get(RuleIndex.class).getByKey(RULE_KEY);
+ assertThat(indexedRule.debtCharacteristicKey()).isEqualTo("RELIABILITY");
+ assertThat(indexedRule.debtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY");
+ assertThat(indexedRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE);
+ assertThat(indexedRule.debtRemediationFunction().coefficient()).isNull();
+ assertThat(indexedRule.debtRemediationFunction().offset()).isEqualTo("10min");
+
+ assertThat(indexedRule.debtOverloaded()).isTrue();
+ assertThat(indexedRule.defaultDebtCharacteristicKey()).isEqualTo("RELIABILITY");
+ assertThat(indexedRule.defaultDebtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY");
+ assertThat(indexedRule.defaultDebtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
+ assertThat(indexedRule.defaultDebtRemediationFunction().coefficient()).isEqualTo("1d");
+ assertThat(indexedRule.defaultDebtRemediationFunction().offset()).isEqualTo("5min");
}
@Test
@@ -301,7 +347,13 @@ public class RuleUpdaterMediumTest {
assertThat(indexedRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
assertThat(indexedRule.debtRemediationFunction().coefficient()).isEqualTo("1d");
assertThat(indexedRule.debtRemediationFunction().offset()).isEqualTo("5min");
+
assertThat(indexedRule.debtOverloaded()).isFalse();
+ assertThat(indexedRule.defaultDebtCharacteristicKey()).isEqualTo("RELIABILITY");
+ assertThat(indexedRule.defaultDebtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY");
+ assertThat(indexedRule.defaultDebtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
+ assertThat(indexedRule.defaultDebtRemediationFunction().coefficient()).isEqualTo("1d");
+ assertThat(indexedRule.defaultDebtRemediationFunction().offset()).isEqualTo("5min");
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java
index 8d763d52e3e..3b7fdb0ed29 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java
@@ -50,8 +50,12 @@ import org.sonar.server.search.Result;
import org.sonar.server.tester.ServerTester;
import javax.annotation.Nullable;
-
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
import static org.fest.assertions.Assertions.assertThat;
@@ -609,7 +613,7 @@ public class RuleIndexMediumTest {
// 4. get all active rules on profile
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto2.getKey()),
+ .setQProfileKey(qualityProfileDto2.getKey()),
new QueryOptions());
assertThat(result.getHits()).hasSize(1);
assertThat(result.getHits().get(0).name()).isEqualTo(rule1.getName());
@@ -646,7 +650,7 @@ public class RuleIndexMediumTest {
ActiveRuleDto.createFor(qualityProfileDto2, rule3)
.setSeverity("BLOCKER")
.setInheritance(ActiveRule.Inheritance.INHERITED.name())
- );
+ );
dbSession.commit();
@@ -668,77 +672,77 @@ public class RuleIndexMediumTest {
// 3. get Inherited Rules on profile1
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto1.getKey())
- .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())),
+ .setQProfileKey(qualityProfileDto1.getKey())
+ .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(0);
// 4. get Inherited Rules on profile2
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto2.getKey())
- .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())),
+ .setQProfileKey(qualityProfileDto2.getKey())
+ .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(2);
// 5. get Overridden Rules on profile1
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto1.getKey())
- .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())),
+ .setQProfileKey(qualityProfileDto1.getKey())
+ .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(0);
// 6. get Overridden Rules on profile2
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto2.getKey())
- .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())),
+ .setQProfileKey(qualityProfileDto2.getKey())
+ .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(1);
// 7. get Inherited AND Overridden Rules on profile1
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto1.getKey())
- .setInheritance(ImmutableSet.of(
- ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())),
+ .setQProfileKey(qualityProfileDto1.getKey())
+ .setInheritance(ImmutableSet.of(
+ ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(0);
// 8. get Inherited AND Overridden Rules on profile2
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto2.getKey())
- .setInheritance(ImmutableSet.of(
- ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())),
+ .setQProfileKey(qualityProfileDto2.getKey())
+ .setInheritance(ImmutableSet.of(
+ ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(3);
// 9. get rules active on profile1 with active severity BLOCKER
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto1.getKey())
- .setActiveSeverities(ImmutableSet.of(
- Severity.BLOCKER.toString())),
+ .setQProfileKey(qualityProfileDto1.getKey())
+ .setActiveSeverities(ImmutableSet.of(
+ Severity.BLOCKER.toString())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(3);
// 10. get rules active on profile2 with active severity MINOR, then BLOCKER + MINOR
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto2.getKey())
- .setActiveSeverities(ImmutableSet.of(
- Severity.MINOR.toString())),
+ .setQProfileKey(qualityProfileDto2.getKey())
+ .setActiveSeverities(ImmutableSet.of(
+ Severity.MINOR.toString())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(1);
result = index.search(new RuleQuery().setActivation(true)
- .setQProfileKey(qualityProfileDto2.getKey())
- .setActiveSeverities(ImmutableSet.of(
- Severity.BLOCKER.toString(), Severity.MINOR.toString())),
+ .setQProfileKey(qualityProfileDto2.getKey())
+ .setActiveSeverities(ImmutableSet.of(
+ Severity.BLOCKER.toString(), Severity.MINOR.toString())),
new QueryOptions()
- );
+ );
assertThat(result.getHits()).hasSize(3);
}
@@ -951,7 +955,24 @@ public class RuleIndexMediumTest {
assertThat(index.search(availableSinceNowQuery, new QueryOptions()).getHits()).hasSize(0);
}
- private static List<String> ruleKeys(List<Rule> rules){
+ @Test
+ public void search_protected_chars() throws InterruptedException {
+ String nameWithProtectedChars = "ja#va&sc\"r:ipt";
+
+ RuleDto ruleDto = RuleTesting.newXooX1().setName(nameWithProtectedChars);
+ dao.insert(dbSession, ruleDto);
+ dbSession.commit();
+
+ Rule rule = index.getByKey(RuleTesting.XOO_X1);
+ assertThat(rule.name()).isEqualTo(nameWithProtectedChars);
+
+ RuleQuery protectedCharsQuery = new RuleQuery().setQueryText(nameWithProtectedChars);
+ List<Rule> results = index.search(protectedCharsQuery, new QueryOptions()).getHits();
+ assertThat(results).hasSize(1);
+ assertThat(results.get(0).key()).isEqualTo(RuleTesting.XOO_X1);
+ }
+
+ private static List<String> ruleKeys(List<Rule> rules) {
return newArrayList(Iterables.transform(rules, new Function<Rule, String>() {
@Override
public String apply(@Nullable Rule input) {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java
index 1fadc233225..fe2cd94eeaa 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java
@@ -25,6 +25,7 @@ import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.DateUtils;
import org.sonar.core.persistence.DbSession;
@@ -34,7 +35,9 @@ import org.sonar.core.qualityprofile.db.QualityProfileDao;
import org.sonar.core.qualityprofile.db.QualityProfileDto;
import org.sonar.core.rule.RuleDto;
import org.sonar.core.rule.RuleParamDto;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
import org.sonar.server.db.DbClient;
+import org.sonar.server.debt.DebtTesting;
import org.sonar.server.qualityprofile.QProfileTesting;
import org.sonar.server.qualityprofile.db.ActiveRuleDao;
import org.sonar.server.rule.RuleTesting;
@@ -61,13 +64,16 @@ public class RulesWebServiceMediumTest {
private static final String API_SHOW_METHOD = "show";
private static final String API_TAGS_METHOD = "tags";
+ DbClient db;
RulesWebService ws;
RuleDao ruleDao;
DbSession session;
+ int reliabilityId, softReliabilityId, hardReliabilityId;
@Before
public void setUp() throws Exception {
tester.clearDbAndIndexes();
+ db = tester.get(DbClient.class);
ruleDao = tester.get(RuleDao.class);
ws = tester.get(RulesWebService.class);
session = tester.get(DbClient.class).openSession(false);
@@ -170,21 +176,101 @@ public class RulesWebServiceMediumTest {
@Test
public void search_debt_rules() throws Exception {
+ insertDebtCharacteristics(session);
+
ruleDao.insert(session, RuleTesting.newXooX1()
- .setDefaultRemediationCoefficient("DefaultCoef")
- .setDefaultRemediationFunction("DefaultFunction")
- .setDefaultRemediationCoefficient("DefaultCoef")
- .setDefaultSubCharacteristicId(1));
+ .setDefaultSubCharacteristicId(hardReliabilityId)
+ .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+ .setDefaultRemediationCoefficient("1h")
+ .setDefaultRemediationOffset("15min")
+
+ .setSubCharacteristicId(null)
+ .setRemediationFunction(null)
+ .setRemediationCoefficient(null)
+ .setRemediationOffset(null)
+ );
session.commit();
MockUserSession.set();
WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD);
- request.setParam(SearchOptions.PARAM_FIELDS, "debtRemFn,debtChar,debtCharName");
+ request.setParam(SearchOptions.PARAM_FIELDS, "debtChar,debtCharName,debtSubChar,debtSubCharName,debtRemFn,debtOverloaded,defaultDebtChar,defaultDebtSubChar,defaultDebtRemFn");
WsTester.Result result = request.execute();
result.assertJson(this.getClass(), "search_debt_rule.json");
}
@Test
+ public void search_debt_rules_with_default_and_overridden_debt_values() throws Exception {
+ insertDebtCharacteristics(session);
+
+ ruleDao.insert(session, RuleTesting.newXooX1()
+ .setDefaultSubCharacteristicId(hardReliabilityId)
+ .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+ .setDefaultRemediationCoefficient("1h")
+ .setDefaultRemediationOffset("15min")
+
+ .setSubCharacteristicId(softReliabilityId)
+ .setRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+ .setRemediationCoefficient("2h")
+ .setRemediationOffset("25min")
+ );
+ session.commit();
+
+ MockUserSession.set();
+ WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD);
+ request.setParam(SearchOptions.PARAM_FIELDS, "debtChar,debtCharName,debtSubChar,debtSubCharName,debtRemFn,debtOverloaded,defaultDebtChar,defaultDebtSubChar,defaultDebtRemFn");
+ WsTester.Result result = request.execute();
+ result.assertJson(this.getClass(), "search_debt_rules_with_default_and_overridden_debt_values.json");
+ }
+
+ @Test
+ public void search_debt_rules_with_default_linear_offset_and_overridden_constant_debt() throws Exception {
+ insertDebtCharacteristics(session);
+
+ ruleDao.insert(session, RuleTesting.newXooX1()
+ .setDefaultSubCharacteristicId(hardReliabilityId)
+ .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+ .setDefaultRemediationCoefficient("1h")
+ .setDefaultRemediationOffset("15min")
+
+ .setSubCharacteristicId(softReliabilityId)
+ .setRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE.name())
+ .setRemediationCoefficient(null)
+ .setRemediationOffset("5min")
+ );
+ session.commit();
+
+ MockUserSession.set();
+ WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD);
+ request.setParam(SearchOptions.PARAM_FIELDS, "debtChar,debtCharName,debtSubChar,debtSubCharName,debtRemFn,debtOverloaded,defaultDebtChar,defaultDebtSubChar,defaultDebtRemFn");
+ WsTester.Result result = request.execute();
+ result.assertJson(this.getClass(), "search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json");
+ }
+
+ @Test
+ public void search_debt_rules_with_default_linear_offset_and_overridden_linear_debt() throws Exception {
+ insertDebtCharacteristics(session);
+
+ ruleDao.insert(session, RuleTesting.newXooX1()
+ .setDefaultSubCharacteristicId(hardReliabilityId)
+ .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+ .setDefaultRemediationCoefficient("1h")
+ .setDefaultRemediationOffset("15min")
+
+ .setSubCharacteristicId(softReliabilityId)
+ .setRemediationFunction(DebtRemediationFunction.Type.LINEAR.name())
+ .setRemediationCoefficient("1h")
+ .setRemediationOffset(null)
+ );
+ session.commit();
+
+ MockUserSession.set();
+ WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD);
+ request.setParam(SearchOptions.PARAM_FIELDS, "debtChar,debtCharName,debtSubChar,debtSubCharName,debtRemFn,debtOverloaded,defaultDebtChar,defaultDebtSubChar,defaultDebtRemFn");
+ WsTester.Result result = request.execute();
+ result.assertJson(this.getClass(), "search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json");
+ }
+
+ @Test
public void search_template_rules() throws Exception {
RuleDto templateRule = RuleTesting.newXooX1().setIsTemplate(true);
ruleDao.insert(session, templateRule);
@@ -430,4 +516,20 @@ public class RulesWebServiceMediumTest {
.setInheritance(null)
.setSeverity("BLOCKER");
}
+
+ private void insertDebtCharacteristics(DbSession dbSession) {
+ CharacteristicDto reliability = DebtTesting.newCharacteristicDto("RELIABILITY").setName("Reliability");
+ db.debtCharacteristicDao().insert(reliability, dbSession);
+ reliabilityId = reliability.getId();
+
+ CharacteristicDto softReliability = DebtTesting.newCharacteristicDto("SOFT_RELIABILITY").setName("Soft Reliability")
+ .setParentId(reliability.getId());
+ db.debtCharacteristicDao().insert(softReliability, dbSession);
+ softReliabilityId = softReliability.getId();
+
+ CharacteristicDto hardReliability = DebtTesting.newCharacteristicDto("HARD_RELIABILITY").setName("Hard Reliability")
+ .setParentId(reliability.getId());
+ db.debtCharacteristicDao().insert(hardReliability, dbSession);
+ hardReliabilityId = hardReliability.getId();
+ }
}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_active_rules.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_active_rules.json
new file mode 100644
index 00000000000..f7f8faf299b
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_active_rules.json
@@ -0,0 +1,25 @@
+{
+ "timestamp": 0,
+ "qprofilesByLanguage": {
+ "java": {
+ "key": "abcd",
+ "name": "Default",
+ "language": "java",
+ "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM"
+ }
+ },
+ "activeRules": [
+ {
+ "repositoryKey": "squid",
+ "ruleKey": "AvoidCycle",
+ "name": "Avoid Cycle",
+ "severity": "MINOR",
+ "internalKey": "squid-1",
+ "language": "java",
+ "params": {
+ "max" : "2"
+ }
+ }
+ ],
+ "settingsByModule": {}
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json
index 1505da0346b..3f83f0921a3 100644
--- a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json
+++ b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json
@@ -1,8 +1,18 @@
{"total": 1, "p": 1, "ps": 10, "rules": [
{
"key": "xoo:x1",
- "debtRemFnType": "LINEAR",
+ "debtChar": "RELIABILITY",
+ "debtCharName": "Reliability",
+ "debtSubChar": "HARD_RELIABILITY",
+ "debtSubCharName": "Hard Reliability",
+ "debtRemFnType": "LINEAR_OFFSET",
"debtRemFnCoeff": "1h",
- "debtRemFnOffset": "5min"
+ "debtRemFnOffset": "15min",
+ "debtOverloaded": false,
+ "defaultDebtChar": "RELIABILITY",
+ "defaultDebtSubChar": "HARD_RELIABILITY",
+ "defaultDebtRemFnType": "LINEAR_OFFSET",
+ "defaultDebtRemFnCoeff": "1h",
+ "defaultDebtRemFnOffset": "15min"
}
]}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json
new file mode 100644
index 00000000000..3aa6211c764
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json
@@ -0,0 +1,18 @@
+{"total": 1, "p": 1, "ps": 10, "rules": [
+ {
+ "key": "xoo:x1",
+ "debtChar": "RELIABILITY",
+ "debtCharName": "Reliability",
+ "debtSubChar": "SOFT_RELIABILITY",
+ "debtSubCharName": "Soft Reliability",
+ "debtRemFnType": "LINEAR_OFFSET",
+ "debtRemFnCoeff": "2h",
+ "debtRemFnOffset": "25min",
+ "debtOverloaded": true,
+ "defaultDebtChar": "RELIABILITY",
+ "defaultDebtSubChar": "HARD_RELIABILITY",
+ "defaultDebtRemFnType": "LINEAR_OFFSET",
+ "defaultDebtRemFnCoeff": "1h",
+ "defaultDebtRemFnOffset": "15min"
+ }
+]}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json
new file mode 100644
index 00000000000..6ae75abecb5
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json
@@ -0,0 +1,17 @@
+{"total": 1, "p": 1, "ps": 10, "rules": [
+ {
+ "key": "xoo:x1",
+ "debtChar": "RELIABILITY",
+ "debtCharName": "Reliability",
+ "debtSubChar": "SOFT_RELIABILITY",
+ "debtSubCharName": "Soft Reliability",
+ "debtRemFnType": "CONSTANT_ISSUE",
+ "debtRemFnOffset": "5min",
+ "debtOverloaded": true,
+ "defaultDebtChar": "RELIABILITY",
+ "defaultDebtSubChar": "HARD_RELIABILITY",
+ "defaultDebtRemFnType": "LINEAR_OFFSET",
+ "defaultDebtRemFnCoeff": "1h",
+ "defaultDebtRemFnOffset": "15min"
+ }
+]}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json
new file mode 100644
index 00000000000..b1c457e13c0
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json
@@ -0,0 +1,17 @@
+{"total": 1, "p": 1, "ps": 10, "rules": [
+ {
+ "key": "xoo:x1",
+ "debtChar": "RELIABILITY",
+ "debtCharName": "Reliability",
+ "debtSubChar": "SOFT_RELIABILITY",
+ "debtSubCharName": "Soft Reliability",
+ "debtRemFnType": "LINEAR",
+ "debtRemFnCoeff": "1h",
+ "debtOverloaded": true,
+ "defaultDebtChar": "RELIABILITY",
+ "defaultDebtSubChar": "HARD_RELIABILITY",
+ "defaultDebtRemFnType": "LINEAR_OFFSET",
+ "defaultDebtRemFnCoeff": "1h",
+ "defaultDebtRemFnOffset": "15min"
+ }
+]}
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee
index d1975327285..e222dcb4112 100644
--- a/server/sonar-web/Gruntfile.coffee
+++ b/server/sonar-web/Gruntfile.coffee
@@ -1,8 +1,4 @@
module.exports = (grunt) ->
- grunt.loadNpmTasks('grunt-karma')
- grunt.loadNpmTasks('grunt-express-server')
- grunt.loadNpmTasks('grunt-casper')
-
pkg = grunt.file.readJSON('package.json')
grunt.initConfig
@@ -273,11 +269,20 @@ module.exports = (grunt) ->
options:
test: true
'no-colors': true
+ concise: true
src: ['<%= pkg.sources %>js/tests/e2e/tests/**/*.js']
cw:
options:
test: true
+ parallel: true,
+ concurrency: 5
src: ['<%= pkg.sources %>js/tests/e2e/tests/component-viewer-spec.js']
+ single:
+ options:
+ test: true
+ parallel: true,
+ concurrency: 5
+ src: ['<%= pkg.sources %>js/tests/e2e/tests/<%= grunt.option("spec") %>-spec.js']
watch:
@@ -313,6 +318,8 @@ module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-contrib-clean'
grunt.loadNpmTasks 'grunt-contrib-copy'
grunt.loadNpmTasks 'grunt-contrib-concat'
+ grunt.loadNpmTasks 'grunt-express-server'
+ grunt.loadNpmTasks 'grunt-casper'
# Define tasks
@@ -333,3 +340,6 @@ module.exports = (grunt) ->
grunt.registerTask 'cw', ['clean:js', 'coffee:build', 'handlebars:build', 'copy:js', 'concat:dev',
'express:test', 'casper:cw']
+
+ grunt.registerTask 'single', ['clean:js', 'coffee:build', 'handlebars:build', 'copy:js', 'concat:dev',
+ 'express:test', 'casper:single']
diff --git a/server/sonar-web/pom.xml b/server/sonar-web/pom.xml
index 881f84e83f1..a90aad2498f 100644
--- a/server/sonar-web/pom.xml
+++ b/server/sonar-web/pom.xml
@@ -351,6 +351,7 @@
<sonar.log.console>true</sonar.log.console>
<sonar.log.profilingLevel>BASIC</sonar.log.profilingLevel>
<sonar.web.context>/dev</sonar.web.context>
+ <sonar.updatecenter.activate>false</sonar.updatecenter.activate>
</systemProperties>
</configuration>
</plugin>
diff --git a/server/sonar-web/src/main/coffee/component-viewer/app.coffee b/server/sonar-web/src/main/coffee/component-viewer/app.coffee
index d091a396aa6..30d4c76ffc5 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/app.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/app.coffee
@@ -5,7 +5,6 @@ requirejs.config
'backbone': 'third-party/backbone'
'backbone.marionette': 'third-party/backbone.marionette'
'handlebars': 'third-party/handlebars'
- 'jquery.mockjax': 'third-party/jquery.mockjax'
shim:
'backbone.marionette':
diff --git a/server/sonar-web/src/main/coffee/component-viewer/header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header.coffee
index 44fdef9b4e6..49d80413125 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/header.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/header.coffee
@@ -155,16 +155,14 @@ define [
main: @options.main, state: @state, component: @component, settings: @settings, source: @model, header: @
@ui.expandedBar.addClass 'active'
@ui.expandLinks.filter("[data-scope=#{scope}]").addClass 'active'
- activeHeaderItem = @state.get 'activeHeaderItem'
- if activeHeaderItem
- @$(activeHeaderItem).addClass 'active'
@options.main.fitIntoElement()
enableBarItem: (item, silent = false) ->
$item = @$(item)
+ console.log item, $item
if $item.length > 0
- if silent then @$(item).addClass('active') else @$(item).click()
+ if silent then @$(item).addClass('active') else @$(item).click()
else
@options.main.hideAllLines()
@@ -206,6 +204,14 @@ define [
@$('.component-viewer-header-time-changes').html '<i class="spinner spinner-margin"></i>'
+ unsetFilter: ->
+ @options.main.resetIssues()
+ @options.main.showAllLines()
+ @state.unset 'activeHeaderItem'
+ @$('.item.active').removeClass 'active'
+ @render()
+
+
filterLines: (e, methodName, extra) ->
@$('.component-viewer-header-expanded-bar-section-list .active').removeClass 'active'
$(e.currentTarget).addClass 'active'
diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee
index e39ebeca502..09190ce58a2 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee
@@ -19,10 +19,12 @@ define [
filterByLines: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByLines'
@state.set 'activeHeaderItem', '.js-filter-lines'
filterByNcloc: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByNcloc'
@state.set 'activeHeaderItem', '.js-filter-ncloc'
diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee
index 330f8b0f71a..b0258f1ac85 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee
@@ -48,61 +48,73 @@ define [
filterByLinesToCover: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByLinesToCover'
@state.set 'activeHeaderItem', '.js-filter-lines-to-cover'
filterByUncoveredLines: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByUncoveredLines'
@state.set 'activeHeaderItem', '.js-filter-uncovered-lines'
filterByBranchesToCover: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByBranchesToCover'
@state.set 'activeHeaderItem', '.js-filter-branches-to-cover'
filterByUncoveredBranches: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByUncoveredBranches'
@state.set 'activeHeaderItem', '.js-filter-uncovered-branches'
filterByLinesToCoverIT: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByLinesToCoverIT'
@state.set 'activeHeaderItem', '.js-filter-lines-to-cover-it'
filterByUncoveredLinesIT: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByUncoveredLinesIT'
@state.set 'activeHeaderItem', '.js-filter-uncovered-lines-it'
filterByBranchesToCoverIT: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByBranchesToCoverIT'
@state.set 'activeHeaderItem', '.js-filter-branches-to-cover-it'
filterByUncoveredBranchesIT: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByUncoveredBranchesIT'
@state.set 'activeHeaderItem', '.js-filter-uncovered-branches-it'
filterByLinesToCoverOverall: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByLinesToCoverOverall'
@state.set 'activeHeaderItem', '.js-filter-lines-to-cover-overall'
filterByUncoveredLinesOverall: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByUncoveredLinesOverall'
@state.set 'activeHeaderItem', '.js-filter-uncovered-lines-overall'
filterByBranchesToCoverOverall: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByBranchesToCoverOverall'
@state.set 'activeHeaderItem', '.js-filter-branches-to-cover-overall'
filterByUncoveredBranchesOverall: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByUncoveredBranchesOverall'
@state.set 'activeHeaderItem', '.js-filter-uncovered-branches-overall'
diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee
index 8cb931878af..f0f4f3bba20 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee
@@ -8,6 +8,8 @@ define [
BaseHeaderView
) ->
+ $ = jQuery
+
class extends BaseHeaderView
template: Templates['cw-duplications-header']
@@ -18,5 +20,6 @@ define [
filterByDuplications: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByDuplications'
@state.set 'activeHeaderItem', '.js-filter-duplications'
diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee
index f7d24a90601..b765c2f3cc4 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee
@@ -63,62 +63,74 @@ define [
filterByCurrentIssue: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByCurrentIssue'
@state.set 'activeHeaderItem', '.js-filter-current-issues'
filterByAllIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByAllIssues'
@state.set 'activeHeaderItem', '.js-filter-all-issues'
filterByFixedIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByFixedIssues'
@state.set 'activeHeaderItem', '.js-filter-fixed-issues'
filterByUnresolvedIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByUnresolvedIssues'
@state.set 'activeHeaderItem', '.js-filter-unresolved-issues'
filterByFalsePositiveIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByFalsePositiveIssues'
@state.set 'activeHeaderItem', '.js-filter-false-positive-issues'
filterByOpenIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByOpenIssues'
@state.set 'activeHeaderItem', '.js-filter-open-issues'
filterByRule: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
rule = $(e.currentTarget).data 'rule'
@header.filterLines e, 'filterByRule', rule
@state.set 'activeHeaderItem', ".js-filter-rule[data-rule='#{rule}']"
filterByBlockerIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByBlockerIssues'
@state.set 'activeHeaderItem', '.js-filter-BLOCKER-issues'
filterByCriticalIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByCriticalIssues'
@state.set 'activeHeaderItem', '.js-filter-CRITICAL-issues'
filterByMajorIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByMajorIssues'
@state.set 'activeHeaderItem', '.js-filter-MAJOR-issues'
filterByMinorIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByMinorIssues'
@state.set 'activeHeaderItem', '.js-filter-MINOR-issues'
filterByInfoIssues: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
@header.filterLines e, 'filterByInfoIssues'
@state.set 'activeHeaderItem', '.js-filter-INFO-issues'
diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee
index 66b43636a47..a3f9c2f68a1 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee
@@ -19,6 +19,7 @@ define [
events:
'click .js-scm-time-changes': 'scmTimeChanges'
+ 'click .js-filter-modified-lines': 'filterBySCM'
scmTimeChanges: (e) ->
@@ -29,7 +30,13 @@ define [
main: @options.main
bottom: true
popup.render()
- popup.on 'change', (period) => @main.enablePeriod period
+ popup.on 'change', (period) => @main.enablePeriod period, '.js-filter-modified-lines'
+
+
+ filterBySCM: (e) ->
+ return @header.unsetFilter() if $(e.currentTarget).is('.active')
+ @header.filterLines e, 'filterBySCM'
+ @state.set 'activeHeaderItem', '.js-filter-modified-lines'
serializeData: ->
diff --git a/server/sonar-web/src/main/coffee/component-viewer/main.coffee b/server/sonar-web/src/main/coffee/component-viewer/main.coffee
index a7f4c7415a2..6dd645d853b 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/main.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/main.coffee
@@ -16,8 +16,6 @@ define [
'component-viewer/source'
'component-viewer/header'
'component-viewer/utils'
-
- 'component-viewer/mockjax'
], (
Backbone
Marionette
@@ -352,8 +350,7 @@ define [
$.when(@requestMeasures(@key, period?.get('key')), @requestIssuesPeriod(@key, period?.get('key')), @requestSCM(@key)).done =>
if activeHeaderItem?
@state.set 'activeHeaderItem', activeHeaderItem
- @headerView.render()
- else @filterBySCM()
+ @headerView.render()
addTransition: (transition, options) ->
@@ -391,8 +388,10 @@ define [
filterByLines: ->
+ @resetIssues()
@showAllLines()
filterByNcloc: ->
+ @resetIssues()
@_filterByLines (line) -> line?.executable
diff --git a/server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee b/server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee
index 63a4bc5f78a..971074c918f 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee
@@ -51,6 +51,7 @@ define [], () ->
if @settings.get('issues') && !@state.get('hasIssues')
requests.push @requestIssues @key
$.when.apply($, requests).done =>
+ @resetIssues()
@_filterByCoverage(predicate)
@@ -59,6 +60,7 @@ define [], () ->
if @settings.get('issues') && !@state.get('hasIssues')
requests.push @requestIssues @key
$.when.apply($, requests).done =>
+ @resetIssues()
@_filterByCoverage(predicate)
@@ -67,6 +69,7 @@ define [], () ->
if @settings.get('issues') && !@state.get('hasIssues')
requests.push @requestIssues @key
$.when.apply($, requests).done =>
+ @resetIssues()
@_filterByCoverage(predicate)
diff --git a/server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee b/server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee
index 9feefc4770d..477420b5d29 100644
--- a/server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee
+++ b/server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee
@@ -20,8 +20,14 @@ define [
$.get API_ISSUES, options, (data) =>
@state.set 'hasIssues', true
issues = _.sortBy data.issues, (issue) -> "#{issue.rule}_____#{issue.message}"
+ @source.set issues: issues
+ @resetIssues()
+
+
+ resetIssues: ->
+ issues = @source.get('issues')
+ if _.isArray issues
@source.set
- issues: issues
activeIssues: issues.filter (issue) -> !issue.resolution
diff --git a/server/sonar-web/src/main/coffee/component-viewer/mockjax.coffee b/server/sonar-web/src/main/coffee/component-viewer/mockjax.coffee
deleted file mode 100644
index 65137174334..00000000000
--- a/server/sonar-web/src/main/coffee/component-viewer/mockjax.coffee
+++ /dev/null
@@ -1,27 +0,0 @@
-define ['third-party/jquery.mockjax'], ->
-
- jQuery.mockjaxSettings.contentType = 'text/json';
- jQuery.mockjaxSettings.responseTime = 250;
-
- jQuery.mockjax
- url: "#{baseUrl}/api/sources/app"
- responseText: JSON.stringify
- key: 'org.codehaus.sonar:sonar-dev-maven-plugin:src/main/java/org/sonar/dev/UploadMojo.java'
- path: 'src/main/java/org/sonar/dev/UploadMojo.java'
- name: 'UploadMojo.java'
- q: 'FIL'
- fav: false
- project: 'org.codehaus.sonar:sonar-dev-maven-plugin'
- projectName: 'SonarQube Development Maven Plugin'
- periods: []
- measures:
- 'ncloc': 69
- 'coverage': '30%'
- 'duplication_density': '7.4%'
- 'debt': '3d 2h'
- 'issues': 4
- 'blocker_issues': 1
- 'critical_issues': 2
- 'major_issues': 0
- 'minor_issues': 1
- 'info_issues': 0 \ No newline at end of file
diff --git a/server/sonar-web/src/main/coffee/dashboard/file-app.coffee b/server/sonar-web/src/main/coffee/dashboard/file-app.coffee
index 661562cef33..0e35c084710 100644
--- a/server/sonar-web/src/main/coffee/dashboard/file-app.coffee
+++ b/server/sonar-web/src/main/coffee/dashboard/file-app.coffee
@@ -5,7 +5,6 @@ requirejs.config
'backbone': 'third-party/backbone'
'backbone.marionette': 'third-party/backbone.marionette'
'handlebars': 'third-party/handlebars'
- 'jquery.mockjax': 'third-party/jquery.mockjax'
shim:
'backbone.marionette':
diff --git a/server/sonar-web/src/main/coffee/drilldown/app.coffee b/server/sonar-web/src/main/coffee/drilldown/app.coffee
index fc62830b711..c1179e3e05d 100644
--- a/server/sonar-web/src/main/coffee/drilldown/app.coffee
+++ b/server/sonar-web/src/main/coffee/drilldown/app.coffee
@@ -5,7 +5,6 @@ requirejs.config
'backbone': 'third-party/backbone'
'backbone.marionette': 'third-party/backbone.marionette'
'handlebars': 'third-party/handlebars'
- 'jquery.mockjax': 'third-party/jquery.mockjax'
shim:
'backbone.marionette':
diff --git a/server/sonar-web/src/main/coffee/drilldown/conf.coffee b/server/sonar-web/src/main/coffee/drilldown/conf.coffee
index b5f31642c18..db5ee65a97f 100644
--- a/server/sonar-web/src/main/coffee/drilldown/conf.coffee
+++ b/server/sonar-web/src/main/coffee/drilldown/conf.coffee
@@ -34,20 +34,20 @@ define
'new_it_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches-it' }
# Overall
- 'overall_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover' }
- 'overall_lines_to_cover': { tab: 'coverage', item: '.js-filter-lines-to-cover' }
- 'overall_uncovered_lines': { tab: 'coverage', item: '.js-filter-uncovered-lines' }
- 'overall_line_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover' }
- 'overall_conditions_to_cover': { tab: 'coverage', item: '.js-filter-branches-to-cover' }
- 'overall_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches' }
- 'overall_branch_coverage': { tab: 'coverage', item: '.js-filter-branches-to-cover' }
- 'new_overall_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover' }
- 'new_overall_uncovered_lines': { tab: 'coverage', item: '.js-filter-uncovered-lines' }
- 'new_overall_line_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover' }
- 'new_overall_lines_to_cover': { tab: 'coverage', item: '.js-filter-lines-to-cover' }
- 'new_overall_branch_coverage': { tab: 'coverage', item: '.js-filter-branches-to-cover' }
- 'new_overall_conditions_to_cover': { tab: 'coverage', item: '.js-filter-branches-to-cover' }
- 'new_overall_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches' }
+ 'overall_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' }
+ 'overall_lines_to_cover': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' }
+ 'overall_uncovered_lines': { tab: 'coverage', item: '.js-filter-uncovered-lines-overall' }
+ 'overall_line_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' }
+ 'overall_conditions_to_cover': { tab: 'coverage', item: '.js-filter-branches-to-cover-overall' }
+ 'overall_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches-overall' }
+ 'overall_branch_coverage': { tab: 'coverage', item: '.js-filter-branches-to-cover-overall' }
+ 'new_overall_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' }
+ 'new_overall_uncovered_lines': { tab: 'coverage', item: '.js-filter-uncovered-lines-overall' }
+ 'new_overall_line_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' }
+ 'new_overall_lines_to_cover': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' }
+ 'new_overall_branch_coverage': { tab: 'coverage', item: '.js-filter-branches-to-cover-overall' }
+ 'new_overall_conditions_to_cover': { tab: 'coverage', item: '.js-filter-branches-to-cover-overall' }
+ 'new_overall_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches-overall' }
# Issues
diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs
index f839bb8963c..b64e0c037ef 100644
--- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs
+++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs
@@ -49,31 +49,6 @@
{{/if}}
</ul>
-{{#if subcharacteristic}}
-<div class="coding-rules-subcharacteristic-more inline-help">
- <ul>
- {{#if debtRemFnType}}
- <li>
- <h3>{{t 'coding_rules.remediation_function'}}</h3>
- {{t 'coding_rules.remediation_function' debtRemFnType}}
- </li>
- {{/if}}
- {{#if debtRemFnCoeff}}
- <li>
- <h3>{{t 'coding_rules.remediation_function.coeff'}}</h3>
- {{debtRemFnCoeff}}
- </li>
- {{/if}}
- {{#if debtRemFnOffset}}
- <li>
- <h3>{{t 'coding_rules.remediation_function.offset'}}</h3>
- {{debtRemFnOffset}}
- </li>
- {{/if}}
- </ul>
-</div>
-{{/if}}
-
<div class="coding-rules-detail-description rule-desc markdown">{{{htmlDesc}}}</div>
{{#if isEditable}}
diff --git a/server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs b/server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs
index 79c44c4460f..e8f68051bf9 100644
--- a/server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs
+++ b/server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs
@@ -5,7 +5,7 @@
{{else}}
{{#if state.duplicationsInDeletedFiles}}
- <p class="marginbottom10">{{t 'duplications.dups_found_on_deleted_resource'}}</p>
+ <p class="marginbottom10 js-duplications-in-deleted-files">{{t 'duplications.dups_found_on_deleted_resource'}}</p>
{{/if}}
{{#if issuesLimitReached}}
diff --git a/server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs b/server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs
index 1c0137f9f12..781b1ceba55 100644
--- a/server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs
+++ b/server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs
@@ -14,28 +14,24 @@
<ul class="component-viewer-workspace-list">
{{#eachReverse workspace}}
<li class="component-viewer-workspace-item {{#if active}}active{{/if}}">
- {{qualifierIcon component.q}}
- <a class="link-action" data-key="{{component.key}}">{{component.name}}</a>
<div class="text-ellipsis subtitle" title="{{component.projectName}} / {{component.subProjectName}}">
{{component.projectName}} / {{component.subProjectName}}
</div>
- {{#if component.dir}}
- <div class="text-ellipsis subtitle">{{component.dir}}</div>
- {{/if}}
+ {{#if component.dir}}<div class="text-ellipsis subtitle">{{component.dir}}</div>{{/if}}
+ {{qualifierIcon component.q}} <a class="link-action" data-key="{{component.key}}">{{component.name}}</a>
+
{{#if options}}
<div class="component-viewer-workspace-transition">{{t 'component_viewer.transition' ../transition}}</div>
<ul class="component-viewer-workspace-options">
{{#each options}}
<li class="component-viewer-workspace-option text-ellipsis {{#if active}}active{{/if}}" title="{{name}}">
- <a class="link-action" data-workspace-key="{{../component.key}}" data-key="{{key}}">{{name}}</a>
{{#if component}}
<div class="text-ellipsis subtitle" title="{{component.projectName}} / {{component.subProjectName}}">
{{component.projectName}} / {{component.subProjectName}}
</div>
{{/if}}
- {{#if subname}}
- <div class="text-ellipsis subtitle" title="{{subname}}">{{subname}}</div>
- {{/if}}
+ {{#if subname}}<div class="text-ellipsis subtitle" title="{{subname}}">{{subname}}</div>{{/if}}
+ <a class="link-action" data-workspace-key="{{../component.key}}" data-key="{{key}}">{{name}}</a>
</li>
{{/each}}
</ul>
diff --git a/server/sonar-web/src/main/js/navigator/filters/choice-filters.js b/server/sonar-web/src/main/js/navigator/filters/choice-filters.js
index 3dec5f94708..b4553847159 100644
--- a/server/sonar-web/src/main/js/navigator/filters/choice-filters.js
+++ b/server/sonar-web/src/main/js/navigator/filters/choice-filters.js
@@ -31,7 +31,7 @@ define([
collection.each(function (item) {
container.append(
that.itemTemplate(_.extend(item.toJSON(), {
- multiple: that.model.get('multiple')
+ multiple: that.model.get('multiple') && item.get('id')[0] !== '!'
}))
);
});
diff --git a/server/sonar-web/src/main/js/tests/e2e/lib.js b/server/sonar-web/src/main/js/tests/e2e/lib.js
index 20ba1903c6c..6650aa509de 100644
--- a/server/sonar-web/src/main/js/tests/e2e/lib.js
+++ b/server/sonar-web/src/main/js/tests/e2e/lib.js
@@ -22,8 +22,10 @@ exports.initMessages = function () {
exports.changeWorkingDirectory = function (dir) {
+ var commandLineArgs = require('system').args;
// Since Casper has control, the invoked script is deep in the argument stack
- var currentFile = require('system').args[4];
+ // commandLineArgs = casper/bin/bootstrap.js,--casper-path=.../casperjs,--cli,--test,[file(s) under test],[options]
+ var currentFile = commandLineArgs[4];
var curFilePath = fs.absolute(currentFile).split(fs.separator);
if (curFilePath.length > 1) {
curFilePath.pop(); // PhantomJS does not have an equivalent path.baseName()-like method
@@ -33,6 +35,16 @@ exports.changeWorkingDirectory = function (dir) {
};
+exports.testName = function () {
+ var head = Array.prototype.slice.call(arguments, 0);
+ return function () {
+ var tail = Array.prototype.slice.call(arguments, 0),
+ body = head.concat(tail);
+ return body.join(' :: ');
+ };
+};
+
+
var mockRequest = function (url, response) {
return casper.evaluate(function (url, response) {
return jQuery.mockjax({ url: url, responseText: response});
@@ -74,3 +86,12 @@ exports.setDefaultViewport = function () {
exports.capture = function (fileName) {
casper.capture(fileName, { top: 0, left: 0, width: WINDOW_WIDTH, height: WINDOW_HEIGHT });
};
+
+
+exports.waitForElementCount = function (selector, count, callback) {
+ return casper.waitFor(function () {
+ return casper.evaluate(function (selector, count) {
+ return document.querySelectorAll(selector).length === count;
+ }, selector, count)
+ }, callback);
+};
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js
index e2cf097fab7..bc7a0cb2fd6 100644
--- a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js
@@ -1,11 +1,12 @@
-var lib = require('../lib');
+var lib = require('../lib'),
+ testName = lib.testName('Coding Rules');
lib.initMessages();
lib.changeWorkingDirectory('coding-rules-spec');
-casper.test.begin('Coding Rules - Readonly Tests', function suite(test) {
+casper.test.begin(testName('Readonly Tests'), function suite(test) {
var appId = null;
var showId = null;
@@ -44,6 +45,7 @@ casper.test.begin('Coding Rules - Readonly Tests', function suite(test) {
test.assertSelectorHasText('ol.navigator-results-list li.active', 'BETA');
});
+
casper.waitForSelector('h3.coding-rules-detail-header', function showFirstRule() {
test.assertSelectorHasText('h3.coding-rules-detail-header', 'No empty line');
test.assertSelectorHasText('.navigator-details .subtitle', 'squid-xoo:x1');
@@ -53,7 +55,28 @@ casper.test.begin('Coding Rules - Readonly Tests', function suite(test) {
test.assertSelectorHasText('.coding-rules-detail-property:nth-child(4)', 'Testability > Integration level testability');
test.assertSelectorHasText('.coding-rules-detail-property:nth-child(6)', 'SonarQube (Xoo)');
+
+ casper.click('.coding-rules-subcharacteristic');
+ casper.waitForSelector('.coding-rules-debt-popup', function checkDebtPopup() {
+ test.assertElementCount('ul.bubble-popup-list li', 3);
+ test.assertSelectorHasText('.bubble-popup-list li:nth-child(1)', 'LINEAR_OFFSET');
+ test.assertSelectorHasText('.bubble-popup-list li:nth-child(2)', '1h');
+ test.assertSelectorHasText('.bubble-popup-list li:nth-child(3)', '30min');
+ });
+
+
test.assertDoesntExist('button#coding-rules-detail-extend-description');
+
+
+ casper.then(function checkParameters() {
+ test.assertElementCount('.coding-rules-detail-parameter', 3);
+ test.assertVisible('.coding-rules-detail-parameter-description[data-key=acceptWhitespace]');
+ test.assertSelectorHasText('.coding-rules-detail-parameter-description[data-key=acceptWhitespace]', 'Accept whitespace');
+ casper.click('.coding-rules-detail-parameter:nth-child(1) .coding-rules-detail-parameter-name');
+ test.assertNotVisible('.coding-rules-detail-parameter-description[data-key=acceptWhitespace]');
+ casper.click('.coding-rules-detail-parameter:nth-child(1) .coding-rules-detail-parameter-name');
+ test.assertVisible('.coding-rules-detail-parameter-description[data-key=acceptWhitespace]');
+ });
});
});
@@ -75,7 +98,7 @@ casper.test.begin('Coding Rules - Readonly Tests', function suite(test) {
});
});
-casper.test.begin('Coding Rules - Admin Tests', function suite(test) {
+casper.test.begin(testName('Admin Tests'), function suite(test) {
var showId = null;
var updateId = null;
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json
index e3c47578ff2..d360d7b4730 100644
--- a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json
@@ -38,7 +38,20 @@
{
"key": "acceptWhitespace",
"htmlDesc": "<h1>Accept whitespace (<code>\\s|\\t</code>) on the line\n</h1>This property is available so that a line containing only whitespace is not considered empty.<br/><h2>Example with property set to ``false``\n</h2><pre lang=\"xoo\"><code> &lt;- One issue here\n&lt;- And one here</code></pre><br/><br/><h2>Example with property set to ``true``\n</h2><pre lang=\"xoo\"><code> &lt;- No issue here\n&lt;- But one here</code></pre><br/>",
+ "type": "BOOLEAN",
"defaultValue": "false"
+ },
+ {
+ "key": "textParameter",
+ "htmlDesc": "This is a parameter of type TEXT",
+ "type": "TEXT",
+ "defaultValue": "false"
+ },
+ {
+ "key": "skipLines",
+ "htmlDesc": "Skip a number of lines at the beginning of the file",
+ "type": "INTEGER",
+ "defaultValue": "0"
}
]
},
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js
index 1058b8a1838..d01f7992644 100644
--- a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js
@@ -1,10 +1,11 @@
-var lib = require('../lib');
+var lib = require('../lib'),
+ testName = lib.testName('Component Viewer');
lib.initMessages();
lib.changeWorkingDirectory('component-viewer-spec');
-casper.test.begin('Component Viewer Base Tests', function (test) {
+casper.test.begin(testName('Base'), function (test) {
casper
.start(lib.buildUrl('component-viewer#component=component'), function () {
lib.setDefaultViewport();
@@ -12,24 +13,18 @@ casper.test.begin('Component Viewer Base Tests', function (test) {
lib.mockRequest('/api/l10n/index', '{}');
lib.mockRequestFromFile('/api/components/app', 'app.json');
lib.mockRequestFromFile('/api/sources/show', 'source.json');
- lib.mockRequestFromFile('/api/issues/search', 'issues.json');
- lib.mockRequestFromFile('/api/coverage/show', 'coverage.json');
- lib.mockRequestFromFile('/api/duplications/show', 'duplications.json');
- lib.mockRequestFromFile('/api/sources/scm', 'scm.json');
- lib.mockRequest('*', '{}'); // Trick to see all ajax requests
})
.then(function () {
casper.waitForSelector('.component-viewer-source .row', function () {
-
// Check header elements
- test.assertElementCount('.component-viewer-header', 1);
+ test.assertExists('.component-viewer-header');
test.assertSelectorContains('.component-viewer-header-component-project', 'SonarQube');
test.assertSelectorContains('.component-viewer-header-component-project', 'SonarQube :: Batch');
test.assertSelectorContains('.component-viewer-header-component-name',
'src/main/java/org/sonar/batch/index/Cache.java');
- test.assertElementCount('.component-viewer-header-favorite', 1);
- test.assertElementCount('.component-viewer-header-actions', 1);
+ test.assertExists('.component-viewer-header-favorite');
+ test.assertExists('.component-viewer-header-actions');
// Check main measures
test.assertSelectorContains('.js-header-tab-basic', '379');
@@ -37,18 +32,42 @@ casper.test.begin('Component Viewer Base Tests', function (test) {
test.assertSelectorContains('.js-header-tab-issues', '3h 30min');
test.assertSelectorContains('.js-header-tab-issues', '6');
test.assertSelectorContains('.js-header-tab-coverage', '74.3%');
- test.assertElementCount('.js-header-tab-scm', 1);
+ test.assertExists('.js-header-tab-scm');
// Check source
test.assertElementCount('.component-viewer-source .row', 520);
test.assertSelectorContains('.component-viewer-source', 'public class Cache');
// Check workspace
- test.assertElementCount('.component-viewer-workspace', 1);
+ test.assertExists('.component-viewer-workspace');
});
})
- .then(function() {
+ .run(function () {
+ test.done();
+ });
+});
+
+
+casper.test.begin(testName('Decoration'), function (test) {
+ casper
+ .start(lib.buildUrl('component-viewer#component=component'), function () {
+ lib.setDefaultViewport();
+
+ lib.mockRequest('/api/l10n/index', '{}');
+ lib.mockRequestFromFile('/api/components/app', 'app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'source.json');
+ lib.mockRequestFromFile('/api/issues/search', 'issues.json');
+ lib.mockRequestFromFile('/api/coverage/show', 'coverage.json');
+ lib.mockRequestFromFile('/api/duplications/show', 'duplications.json');
+ lib.mockRequestFromFile('/api/sources/scm', 'scm.json');
+ })
+
+ .then(function () {
+ casper.waitForSelector('.component-viewer-source .row');
+ })
+
+ .then(function () {
// Check issues decoration
casper.click('.js-toggle-issues');
casper.waitForSelector('.code-issue', function () {
@@ -57,7 +76,7 @@ casper.test.begin('Component Viewer Base Tests', function (test) {
casper.click('.js-toggle-issues');
casper.waitWhileSelector('.code-issue', function () {
- test.assertElementCount('.code-issue', 0);
+ test.assertDoesntExist('.code-issue');
});
});
})
@@ -74,7 +93,7 @@ casper.test.begin('Component Viewer Base Tests', function (test) {
casper.click('.js-toggle-coverage');
casper.waitWhileSelector('.coverage-green', function () {
- test.assertElementCount('.coverage-green', 0);
+ test.assertDoesntExist('.coverage-green');
});
});
})
@@ -87,7 +106,7 @@ casper.test.begin('Component Viewer Base Tests', function (test) {
casper.click('.js-toggle-duplications');
casper.waitWhileSelector('.duplication-exists', function () {
- test.assertElementCount('.duplication-exists', 0);
+ test.assertDoesntExist('.duplication-exists');
});
});
})
@@ -104,8 +123,148 @@ casper.test.begin('Component Viewer Base Tests', function (test) {
casper.click('.js-toggle-scm');
casper.waitWhileSelector('.scm-author', function () {
- test.assertElementCount('.scm-author', 0);
- test.assertElementCount('.scm-date', 0);
+ test.assertDoesntExist('.scm-author');
+ test.assertDoesntExist('.scm-date');
+ });
+ });
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
+
+
+casper.test.begin(testName('Header'), function (test) {
+ casper
+ .start(lib.buildUrl('component-viewer#component=component'), function () {
+ lib.setDefaultViewport();
+
+ lib.mockRequest('/api/l10n/index', '{}');
+ lib.mockRequestFromFile('/api/components/app', 'app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'source.json');
+ lib.mockRequestFromFile('/api/resources', 'resources.json');
+ })
+
+ .then(function () {
+ casper.waitForSelector('.component-viewer-source .row');
+ })
+
+ .then(function () {
+ // Check issues header and filters
+ casper.click('.js-header-tab-issues');
+ casper.waitForSelector('.js-filter-unresolved-issues', function () {
+ test.assertExists('.js-filter-open-issues');
+ test.assertExists('.js-filter-fixed-issues');
+ test.assertExists('.js-filter-false-positive-issues');
+ test.assertSelectorContains('.js-filter-MAJOR-issues', '1');
+ test.assertSelectorContains('.js-filter-MINOR-issues', '1');
+ test.assertSelectorContains('.js-filter-INFO-issues', '4');
+ test.assertSelectorContains('.js-filter-rule[data-rule="common-java:DuplicatedBlocks"]', '1');
+ test.assertSelectorContains('.js-filter-rule[data-rule="squid:S1192"]', '1');
+ test.assertSelectorContains('.js-filter-rule[data-rule="squid:S1135"]', '4');
+ test.assertExists('.js-issues-time-changes');
+
+ casper.click('.js-header-tab-issues');
+ casper.waitWhileSelector('.js-filter-unresolved-issues', function () {
+ test.assertDoesntExist('.js-filter-open-issues');
+ test.assertDoesntExist('.js-filter-MAJOR-issues');
+ test.assertDoesntExist('.js-filter-rule');
+ });
+ });
+ })
+
+ .then(function () {
+ // Check coverage header and filters
+ casper.click('.js-header-tab-coverage');
+ casper.waitForSelector('.js-filter-lines-to-cover', function () {
+ test.assertExists('.js-filter-uncovered-lines');
+ test.assertExists('.js-filter-branches-to-cover');
+ test.assertExists('.js-filter-uncovered-branches');
+ test.assertSelectorContains('[data-metric="coverage"]', '74.3%');
+ test.assertSelectorContains('[data-metric="line_coverage"]', '74.2%');
+ test.assertSelectorContains('[data-metric="lines_to_cover"]', '194');
+ test.assertSelectorContains('[data-metric="uncovered_lines"]', '50');
+ test.assertSelectorContains('[data-metric="branch_coverage"]', '75.0%');
+ test.assertSelectorContains('[data-metric="conditions_to_cover"]', '16');
+ test.assertSelectorContains('[data-metric="uncovered_conditions"]', '4');
+ test.assertExists('.js-coverage-time-changes');
+
+ casper.click('.js-header-tab-coverage');
+ casper.waitWhileSelector('.js-filter-lines-to-cover', function () {
+ test.assertDoesntExist('.js-filter-uncovered-lines');
+ test.assertDoesntExist('[data-metric="coverage"]');
+ test.assertDoesntExist('[data-metric="branch_coverage"]');
+ });
+ });
+ })
+
+ .then(function () {
+ // Check duplications header and filters
+ casper.click('.js-header-tab-duplications');
+ casper.waitForSelector('.js-filter-duplications', function () {
+ test.assertSelectorContains('[data-metric="duplicated_blocks"]', '2');
+ test.assertSelectorContains('[data-metric="duplicated_lines"]', '30');
+
+ casper.click('.js-header-tab-duplications');
+ casper.waitWhileSelector('.js-filter-duplications', function () {
+ test.assertDoesntExist('[data-metric="duplicated_blocks"]');
+ test.assertDoesntExist('[data-metric="duplicated_lines"]');
+ });
+ });
+ })
+
+ .then(function () {
+ // Check scm header and filters
+ casper.click('.js-header-tab-scm');
+ casper.waitForSelector('.js-filter-modified-lines', function () {
+ test.assertExists('.js-scm-time-changes');
+
+ casper.click('.js-header-tab-scm');
+ casper.waitWhileSelector('.js-filter-modified-lines', function () {
+ test.assertDoesntExist('.js-scm-time-changes');
+ });
+ });
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
+
+
+casper.test.begin(testName('Test File'), function (test) {
+ casper
+ .start(lib.buildUrl('component-viewer#component=component'), function () {
+ lib.setDefaultViewport();
+
+ lib.mockRequest('/api/l10n/index', '{}');
+ lib.mockRequestFromFile('/api/components/app', 'tests/app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'tests/source.json');
+ lib.mockRequestFromFile('/api/resources', 'tests/resources.json');
+ lib.mockRequestFromFile('/api/tests/show', 'tests/tests.json');
+ lib.mockRequestFromFile('/api/tests/covered_files', 'tests/covered-files.json');
+ })
+
+ .then(function () {
+ casper.waitForSelector('.component-viewer-source .row');
+ })
+
+ .then(function () {
+ // Check coverage header and filters
+ casper.click('.js-header-tab-tests');
+ casper.waitForSelector('.js-unit-test', function () {
+ test.assertSelectorContains('[data-metric="test_execution_time"]', '12');
+ test.assertElementCount('.js-unit-test', 2);
+ test.assertSelectorContains('.js-unit-test[data-name="should_return_i"]', 'should_return_i');
+ test.assertSelectorContains('.js-unit-test[data-name="should_return_i"]', '5');
+ test.assertSelectorContains('.js-unit-test[data-name="should_return_to_string"]', 'should_return_to_string');
+ test.assertSelectorContains('.js-unit-test[data-name="should_return_to_string"]', '4');
+
+ casper.click('.js-unit-test[data-name="should_return_to_string"]');
+ casper.waitForSelector('.bubble-popup', function () {
+ test.assertSelectorContains('.bubble-popup', 'Sample.java');
+ test.assertSelectorContains('.bubble-popup', 'src/main/java/sample');
});
});
})
@@ -114,3 +273,261 @@ casper.test.begin('Component Viewer Base Tests', function (test) {
test.done();
});
});
+
+
+casper.test.begin(testName('Go From Coverage to Test File'), function (test) {
+ casper
+ .start(lib.buildUrl('component-viewer#component=component'), function () {
+ lib.setDefaultViewport();
+ lib.mockRequest('/api/l10n/index', '{}');
+ lib.mockRequestFromFile('/api/components/app', 'app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'source.json');
+ lib.mockRequestFromFile('/api/coverage/show', 'coverage.json');
+ lib.mockRequestFromFile('/api/tests/test_cases', 'test-cases.json');
+ })
+
+ .then(function () {
+ casper.waitForSelector('.component-viewer-source .row');
+ })
+
+ .then(function () {
+ casper.click('.js-toggle-coverage');
+ casper.waitForSelector('.coverage-green', function () {
+ casper.click('.coverage-green .coverage-tests');
+ casper.waitForSelector('.bubble-popup', function () {
+ test.assertSelectorContains('.bubble-popup', 'src/test/java/org/sonar/batch/issue/IssueCacheTest.java');
+ test.assertSelectorContains('.bubble-popup', 'should_update_existing_issue');
+ test.assertSelectorContains('.bubble-popup li[title="should_update_existing_issue"]', '293');
+
+ lib.clearRequestMocks();
+ lib.mockRequestFromFile('/api/components/app', 'tests/app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'tests/source.json');
+ lib.mockRequestFromFile('/api/resources', 'tests/resources.json');
+ lib.mockRequest('/api/coverage/show', '{}');
+ lib.mockRequestFromFile('/api/tests/show', 'tests/tests.json');
+ casper.click('.component-viewer-popup-test-file[data-key]');
+
+ casper.waitForSelector('.js-unit-test', function () {
+ test.assertElementCount('.js-unit-test', 2);
+ });
+ });
+ });
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
+
+
+casper.test.begin(testName('Coverage Filters'), function (test) {
+ casper
+ .start(lib.buildUrl('component-viewer#component=component'), function () {
+ lib.setDefaultViewport();
+ lib.mockRequest('/api/l10n/index', '{}');
+ lib.mockRequestFromFile('/api/components/app', 'app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'source.json');
+ lib.mockRequestFromFile('/api/resources', 'resources.json');
+ lib.mockRequestFromFile('/api/coverage/show', 'coverage.json');
+ })
+
+ .then(function () {
+ casper.waitForSelector('.component-viewer-source .row');
+ })
+
+ .then(function () {
+ casper.click('.js-header-tab-coverage');
+ casper.waitForSelector('.js-filter-lines-to-cover');
+ })
+
+ .then(function () {
+ casper.click('.js-filter-lines-to-cover');
+ casper.waitForSelector('.coverage-green', function () {
+ test.assertElementCount('.coverage-green', 149);
+ test.assertElementCount('.coverage-red', 51);
+ test.assertElementCount('.coverage-orange', 2);
+ test.assertElementCount('.component-viewer-source .row', 369);
+ });
+ })
+
+ .then(function () {
+ casper.click('.js-filter-uncovered-lines');
+ casper.waitForSelector('.coverage-green', function () {
+ test.assertElementCount('.coverage-green', 18);
+ test.assertElementCount('.coverage-red', 51);
+ test.assertElementCount('.coverage-orange', 0);
+ test.assertElementCount('.component-viewer-source .row', 136);
+ });
+ })
+
+ .then(function () {
+ casper.click('.js-filter-branches-to-cover');
+ casper.waitForSelector('.coverage-green', function () {
+ test.assertElementCount('.coverage-green', 26);
+ test.assertElementCount('.coverage-red', 4);
+ test.assertElementCount('.coverage-orange', 2);
+ test.assertElementCount('.component-viewer-source .row', 33);
+ });
+ })
+
+ .then(function () {
+ casper.click('.js-filter-uncovered-branches');
+ casper.waitForSelector('.coverage-green', function () {
+ test.assertElementCount('.coverage-green', 6);
+ test.assertElementCount('.coverage-red', 4);
+ test.assertElementCount('.coverage-orange', 2);
+ test.assertElementCount('.component-viewer-source .row', 13);
+ });
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
+
+
+casper.test.begin(testName('Ability to Deselect Filters'), function (test) {
+ casper
+ .start(lib.buildUrl('component-viewer#component=component'), function () {
+ lib.setDefaultViewport();
+ lib.mockRequest('/api/l10n/index', '{}');
+ lib.mockRequestFromFile('/api/components/app', 'app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'source.json');
+ lib.mockRequestFromFile('/api/resources', 'resources.json');
+ lib.mockRequestFromFile('/api/issues/search', 'issues.json');
+ lib.mockRequestFromFile('/api/coverage/show', 'coverage.json');
+ lib.mockRequestFromFile('/api/duplications/show', 'duplications.json');
+ })
+
+ .then(function () {
+ casper.waitForSelector('.component-viewer-source .row');
+ })
+
+ .then(function () {
+ casper.click('.js-header-tab-issues');
+ var testFilter = '.js-filter-unresolved-issues';
+ casper.waitForSelector(testFilter, function () {
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 56, function () {
+ test.assertExists(testFilter + '.active');
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 520, function () {
+ test.assertDoesntExist(testFilter + '.active');
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 56, function () {
+ test.assertExists(testFilter + '.active');
+ });
+ });
+ })
+ });
+ })
+
+ .then(function () {
+ casper.click('.js-header-tab-coverage');
+ var testFilter = '.js-filter-lines-to-cover';
+ casper.waitForSelector(testFilter, function () {
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 369, function () {
+ test.assertExists(testFilter + '.active');
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 520, function () {
+ test.assertDoesntExist(testFilter + '.active');
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 369, function () {
+ test.assertExists(testFilter + '.active');
+ });
+ });
+ })
+ });
+ })
+
+ .then(function () {
+ casper.click('.js-header-tab-duplications');
+ var testFilter = '.js-filter-duplications';
+ casper.waitForSelector(testFilter, function () {
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 39, function () {
+ test.assertExists(testFilter + '.active');
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 520, function () {
+ test.assertDoesntExist(testFilter + '.active');
+ casper.click(testFilter);
+ lib.waitForElementCount('.component-viewer-source .row', 39, function () {
+ test.assertExists(testFilter + '.active');
+ });
+ });
+ })
+ });
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
+
+
+casper.test.begin(testName('Cross-Project Duplications'), function (test) {
+ casper
+ .start(lib.buildUrl('component-viewer#component=component'), function () {
+ lib.setDefaultViewport();
+ lib.mockRequest('/api/l10n/index', '{}');
+ lib.mockRequestFromFile('/api/components/app', 'app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'source.json');
+ lib.mockRequestFromFile('/api/resources', 'resources.json');
+ lib.mockRequestFromFile('/api/duplications/show', 'cross-project-duplications.json');
+ })
+
+ .then(function () {
+ casper.waitForSelector('.component-viewer-source .row');
+ })
+
+ .then(function () {
+ casper.click('.js-header-tab-duplications');
+ casper.waitForSelector('.js-filter-duplications', function () {
+ casper.click('.js-filter-duplications');
+ casper.waitForSelector('.duplication-exists', function () {
+ casper.click('.duplication-exists');
+ casper.waitForSelector('.bubble-popup', function () {
+ test.assertSelectorContains('.bubble-popup', 'JavaScript');
+ test.assertSelectorContains('.bubble-popup', 'JavaScript :: Sonar Plugin');
+ test.assertExists('a[data-key="org.codehaus.sonar-plugins.javascript:sonar-javascript-plugin:src/main/java/org/sonar/plugins/javascript/core/JavaScript.java"]');
+ test.assertSelectorContains('.bubble-popup', 'src/main/java/org/sonar/plugins/javascript/core/JavaScript.java');
+ test.assertSelectorContains('.bubble-popup', '455'); // Line from
+ test.assertSelectorContains('.bubble-popup', '470'); // Line to
+ });
+ });
+ });
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
+
+
+casper.test.begin(testName('Duplications in Deleted Files'), function (test) {
+ casper
+ .start(lib.buildUrl('component-viewer#component=component'), function () {
+ lib.setDefaultViewport();
+ lib.mockRequest('/api/l10n/index', '{}');
+ lib.mockRequestFromFile('/api/components/app', 'app.json');
+ lib.mockRequestFromFile('/api/sources/show', 'source.json');
+ lib.mockRequestFromFile('/api/resources', 'resources.json');
+ lib.mockRequestFromFile('/api/duplications/show', 'duplications-in-deleted-files.json');
+ })
+
+ .then(function () {
+ casper.waitForSelector('.component-viewer-source .row');
+ })
+
+ .then(function () {
+ casper.click('.js-toggle-duplications');
+ casper.waitForSelector('.duplication-exists', function () {
+ test.assertExists('.js-duplications-in-deleted-files');
+ });
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/cross-project-duplications.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/cross-project-duplications.json
new file mode 100644
index 00000000000..cd445d5f5b2
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/cross-project-duplications.json
@@ -0,0 +1,33 @@
+{"duplications": [
+ {
+ "blocks": [
+ {
+ "from": 404,
+ "size": 15,
+ "_ref": "1"
+ },
+ {
+ "from": 455,
+ "size": 15,
+ "_ref": "2"
+ }
+ ]
+ }
+], "files": {
+ "1": {
+ "key": "org.codehaus.sonar:sonar-batch:src/main/java/org/sonar/batch/index/Cache.java",
+ "name": "src/main/java/org/sonar/batch/index/Cache.java",
+ "project": "org.codehaus.sonar:sonar",
+ "projectName": "SonarQube",
+ "subProject": "org.codehaus.sonar:sonar-batch",
+ "subProjectName": "SonarQube :: Batch"
+ },
+ "2": {
+ "key": "org.codehaus.sonar-plugins.javascript:sonar-javascript-plugin:src/main/java/org/sonar/plugins/javascript/core/JavaScript.java",
+ "name": "src/main/java/org/sonar/plugins/javascript/core/JavaScript.java",
+ "project": "org.codehaus.sonar-plugins.javascript:javascript",
+ "projectName": "JavaScript",
+ "subProject": "org.codehaus.sonar-plugins.javascript:sonar-javascript-plugin",
+ "subProjectName": "JavaScript :: Sonar Plugin"
+ }
+}}
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/duplications-in-deleted-files.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/duplications-in-deleted-files.json
new file mode 100644
index 00000000000..9de23d1ce7d
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/duplications-in-deleted-files.json
@@ -0,0 +1,24 @@
+{"duplications": [
+ {
+ "blocks": [
+ {
+ "from": 404,
+ "size": 15,
+ "_ref": "1"
+ },
+ {
+ "from": 455,
+ "size": 15
+ }
+ ]
+ }
+], "files": {
+ "1": {
+ "key": "org.codehaus.sonar:sonar-batch:src/main/java/org/sonar/batch/index/Cache.java",
+ "name": "src/main/java/org/sonar/batch/index/Cache.java",
+ "project": "org.codehaus.sonar:sonar",
+ "projectName": "SonarQube",
+ "subProject": "org.codehaus.sonar:sonar-batch",
+ "subProjectName": "SonarQube :: Batch"
+ }
+}}
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources.json
new file mode 100644
index 00000000000..415718c7cd5
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources.json
@@ -0,0 +1,154 @@
+[
+ {
+ "id": 19983,
+ "key": "org.codehaus.sonar:sonar-batch:src/main/java/org/sonar/batch/index/Cache.java",
+ "name": "Cache.java",
+ "scope": "FIL",
+ "qualifier": "FIL",
+ "date": "2014-07-21T23:18:51+0200",
+ "creationDate": "2013-04-17T04:06:45+0200",
+ "lname": "src/main/java/org/sonar/batch/index/Cache.java",
+ "lang": "java",
+ "msr": [
+ {
+ "key": "lines",
+ "val": 519.0,
+ "frmt_val": "519"
+ },
+ {
+ "key": "ncloc",
+ "val": 379.0,
+ "frmt_val": "379"
+ },
+ {
+ "key": "classes",
+ "val": 6.0,
+ "frmt_val": "6"
+ },
+ {
+ "key": "functions",
+ "val": 56.0,
+ "frmt_val": "56"
+ },
+ {
+ "key": "accessors",
+ "val": 0.0,
+ "frmt_val": "0"
+ },
+ {
+ "key": "statements",
+ "val": 174.0,
+ "frmt_val": "174"
+ },
+ {
+ "key": "public_api",
+ "val": 33.0,
+ "frmt_val": "33"
+ },
+ {
+ "key": "comment_lines",
+ "val": 23.0,
+ "frmt_val": "23"
+ },
+ {
+ "key": "comment_lines_density",
+ "val": 5.7,
+ "frmt_val": "5.7%"
+ },
+ {
+ "key": "public_documented_api_density",
+ "val": 36.4,
+ "frmt_val": "36.4%"
+ },
+ {
+ "key": "public_undocumented_api",
+ "val": 21.0,
+ "frmt_val": "21"
+ },
+ {
+ "key": "complexity",
+ "val": 116.0,
+ "frmt_val": "116"
+ },
+ {
+ "key": "function_complexity",
+ "val": 2.1,
+ "frmt_val": "2.1"
+ },
+ {
+ "key": "coverage",
+ "val": 74.3,
+ "frmt_val": "74.3%"
+ },
+ {
+ "key": "lines_to_cover",
+ "val": 194.0,
+ "frmt_val": "194"
+ },
+ {
+ "key": "uncovered_lines",
+ "val": 50.0,
+ "frmt_val": "50"
+ },
+ {
+ "key": "line_coverage",
+ "val": 74.2,
+ "frmt_val": "74.2%"
+ },
+ {
+ "key": "conditions_to_cover",
+ "val": 16.0,
+ "frmt_val": "16"
+ },
+ {
+ "key": "uncovered_conditions",
+ "val": 4.0,
+ "frmt_val": "4"
+ },
+ {
+ "key": "branch_coverage",
+ "val": 75.0,
+ "frmt_val": "75.0%"
+ },
+ {
+ "key": "duplicated_lines",
+ "val": 30.0,
+ "frmt_val": "30"
+ },
+ {
+ "key": "duplicated_blocks",
+ "val": 2.0,
+ "frmt_val": "2"
+ },
+ {
+ "key": "duplicated_files",
+ "val": 1.0,
+ "frmt_val": "1"
+ },
+ {
+ "key": "duplicated_lines_density",
+ "val": 5.8,
+ "frmt_val": "5.8%"
+ },
+ {
+ "key": "major_violations",
+ "val": 1.0,
+ "frmt_val": "1"
+ },
+ {
+ "key": "minor_violations",
+ "val": 1.0,
+ "frmt_val": "1"
+ },
+ {
+ "key": "info_violations",
+ "val": 4.0,
+ "frmt_val": "4"
+ },
+ {
+ "key": "ncloc_data",
+ "data": "1=0;2=0;3=0;4=0;5=0;6=0;7=0;8=0;9=0;10=0;11=0;12=0;13=0;14=0;15=0;17=0;16=0;19=0;18=0;21=0;20=1;23=1;22=1;25=1;24=1;27=1;26=1;29=1;28=0;31=1;30=0;34=1;35=0;32=1;33=1;38=0;39=0;36=0;37=0;42=0;43=1;40=0;41=1;46=1;47=1;44=1;45=0;51=1;50=0;49=1;48=1;55=0;54=1;53=1;52=1;59=1;58=1;57=1;56=1;63=1;62=1;61=1;60=0;68=1;69=1;70=0;71=1;64=1;65=0;66=1;67=1;76=1;77=1;78=1;79=1;72=1;73=1;74=1;75=1;85=1;84=1;87=1;86=1;81=0;80=0;83=0;82=0;93=1;92=1;95=1;94=1;89=0;88=0;91=0;90=0;102=1;103=1;100=0;101=1;98=0;99=0;96=1;97=0;110=1;111=1;108=0;109=0;106=0;107=0;104=1;105=1;119=1;118=1;117=1;116=1;115=0;114=1;113=1;112=1;127=1;126=0;125=1;124=1;123=1;122=1;121=1;120=1;137=1;136=1;139=1;138=1;141=1;140=0;143=1;142=1;129=1;128=1;131=1;130=0;133=1;132=1;135=0;134=1;152=1;153=1;154=1;155=1;156=0;157=1;158=1;159=1;144=1;145=0;146=1;147=1;148=1;149=1;150=0;151=1;171=1;170=0;169=1;168=1;175=0;174=1;173=1;172=1;163=1;162=1;161=1;160=0;167=1;166=1;165=0;164=1;186=1;187=1;184=1;185=0;190=0;191=0;188=1;189=0;178=1;179=1;176=1;177=1;182=1;183=1;180=0;181=1;205=1;204=0;207=1;206=1;201=1;200=1;203=1;202=1;197=1;196=1;199=0;198=1;193=0;192=0;195=1;194=0;220=1;221=1;222=1;223=1;216=1;217=1;218=1;219=1;212=1;213=1;214=0;215=1;208=1;209=0;210=1;211=1;239=0;238=0;237=0;236=1;235=1;234=1;233=1;232=1;231=1;230=1;229=1;228=0;227=0;226=0;225=0;224=1;254=1;255=1;252=1;253=1;250=1;251=1;248=1;249=1;246=1;247=1;244=0;245=1;242=0;243=0;240=0;241=0;275=1;274=1;273=1;272=1;279=0;278=1;277=1;276=1;283=0;282=0;281=0;280=0;287=1;286=1;285=1;284=0;258=1;259=1;256=1;257=1;262=1;263=1;260=1;261=0;266=1;267=1;264=1;265=1;270=1;271=1;268=1;269=1;305=1;304=1;307=1;306=1;309=1;308=1;311=1;310=1;313=1;312=1;315=1;314=0;317=1;316=1;319=0;318=0;288=1;289=1;290=1;291=1;292=1;293=1;294=1;295=1;296=1;297=1;298=1;299=0;300=0;301=0;302=0;303=1;343=1;342=1;341=1;340=1;339=1;338=1;337=1;336=0;351=1;350=1;349=1;348=1;347=0;346=1;345=1;344=1;326=1;327=1;324=1;325=1;322=1;323=1;320=0;321=0;334=0;335=0;332=1;333=0;330=1;331=1;328=1;329=1;373=1;372=1;375=1;374=0;369=0;368=1;371=1;370=1;381=0;380=1;383=0;382=0;377=1;376=1;379=1;378=1;356=1;357=1;358=1;359=0;352=1;353=0;354=1;355=1;364=0;365=1;366=1;367=1;360=1;361=1;362=1;363=1;410=1;411=1;408=1;409=1;414=1;415=1;412=1;413=1;402=0;403=1;400=1;401=1;406=1;407=0;404=1;405=1;395=1;394=1;393=1;392=0;399=1;398=0;397=1;396=1;387=1;386=1;385=0;384=0;391=1;390=1;389=1;388=0;440=1;441=1;442=1;443=0;444=1;445=1;446=1;447=1;432=1;433=1;434=1;435=1;436=0;437=1;438=1;439=0;425=1;424=1;427=1;426=1;429=1;428=1;431=1;430=0;417=1;416=0;419=1;418=1;421=1;420=1;423=1;422=1;478=1;479=1;476=1;477=1;474=1;475=1;472=1;473=1;470=1;471=1;468=1;469=1;466=1;467=0;464=1;465=1;463=1;462=1;461=1;460=1;459=1;458=0;457=1;456=1;455=1;454=1;453=0;452=1;451=1;450=1;449=0;448=1;508=1;509=1;510=1;511=0;504=1;505=1;506=0;507=1;500=1;501=1;502=0;503=1;496=1;497=0;498=1;499=1;493=0;492=1;495=1;494=1;489=1;488=1;491=1;490=1;485=1;484=1;487=0;486=1;481=1;480=1;483=1;482=1;516=1;517=0;518=1;512=1;513=1;514=1;515=1"
+ }
+ ]
+ }
+]
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/test-cases.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/test-cases.json
new file mode 100644
index 00000000000..233229a803b
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/test-cases.json
@@ -0,0 +1,235 @@
+{"tests": [
+ {
+ "name": "should_update_existing_issue",
+ "status": "OK",
+ "durationInMs": 293,
+ "_ref": "1"
+ },
+ {
+ "name": "testDistributionMeasure",
+ "status": "OK",
+ "durationInMs": 148,
+ "_ref": "2"
+ },
+ {
+ "name": "one_part_key",
+ "status": "OK",
+ "durationInMs": 96,
+ "_ref": "3"
+ },
+ {
+ "name": "testIssueExclusion",
+ "status": "OK",
+ "durationInMs": 305,
+ "_ref": "4"
+ },
+ {
+ "name": "should_add_measure_with_same_metric",
+ "status": "OK",
+ "durationInMs": 141,
+ "_ref": "5"
+ },
+ {
+ "name": "should_create_cache",
+ "status": "OK",
+ "durationInMs": 81,
+ "_ref": "6"
+ },
+ {
+ "name": "scanProjectWithMixedSourcesAndTests",
+ "status": "OK",
+ "durationInMs": 239,
+ "_ref": "7"
+ },
+ {
+ "name": "remove_versus_clear",
+ "status": "OK",
+ "durationInMs": 111,
+ "_ref": "3"
+ },
+ {
+ "name": "should_persist_component_data",
+ "status": "OK",
+ "durationInMs": 132,
+ "_ref": "8"
+ },
+ {
+ "name": "scanProjectWithSourceDir",
+ "status": "OK",
+ "durationInMs": 212,
+ "_ref": "7"
+ },
+ {
+ "name": "should_add_measure_with_too_big_data_for_persistit",
+ "status": "OK",
+ "durationInMs": 336,
+ "_ref": "5"
+ },
+ {
+ "name": "failForDuplicateInputFile",
+ "status": "OK",
+ "durationInMs": 127,
+ "_ref": "7"
+ },
+ {
+ "name": "should_not_create_cache_twice",
+ "status": "OK",
+ "durationInMs": 101,
+ "_ref": "6"
+ },
+ {
+ "name": "should_get_and_set_data",
+ "status": "OK",
+ "durationInMs": 190,
+ "_ref": "9"
+ },
+ {
+ "name": "should_add_input_file",
+ "status": "OK",
+ "durationInMs": 122,
+ "_ref": "10"
+ },
+ {
+ "name": "scanTempProject",
+ "status": "OK",
+ "durationInMs": 345,
+ "_ref": "4"
+ },
+ {
+ "name": "should_get_measures",
+ "status": "OK",
+ "durationInMs": 114,
+ "_ref": "5"
+ },
+ {
+ "name": "test_key_being_prefix_of_another_key",
+ "status": "OK",
+ "durationInMs": 94,
+ "_ref": "3"
+ },
+ {
+ "name": "should_add_measure_with_big_data",
+ "status": "OK",
+ "durationInMs": 242,
+ "_ref": "5"
+ },
+ {
+ "name": "should_get_all_issues",
+ "status": "OK",
+ "durationInMs": 104,
+ "_ref": "1"
+ },
+ {
+ "name": "should_add_measure",
+ "status": "OK",
+ "durationInMs": 117,
+ "_ref": "5"
+ },
+ {
+ "name": "computeMeasuresOnSampleProject",
+ "status": "OK",
+ "durationInMs": 315,
+ "_ref": "2"
+ },
+ {
+ "name": "test_measure_coder",
+ "status": "OK",
+ "durationInMs": 110,
+ "_ref": "5"
+ },
+ {
+ "name": "should_add_measure_with_too_big_data_for_persistit_pre_patch",
+ "status": "OK",
+ "durationInMs": 821,
+ "_ref": "5"
+ },
+ {
+ "name": "three_parts_key",
+ "status": "OK",
+ "durationInMs": 122,
+ "_ref": "3"
+ },
+ {
+ "name": "two_parts_key",
+ "status": "OK",
+ "durationInMs": 118,
+ "_ref": "3"
+ },
+ {
+ "name": "should_add_new_issue",
+ "status": "OK",
+ "durationInMs": 95,
+ "_ref": "1"
+ },
+ {
+ "name": "empty_cache",
+ "status": "OK",
+ "durationInMs": 104,
+ "_ref": "3"
+ },
+ {
+ "name": "scanProjectWithTestDir",
+ "status": "OK",
+ "durationInMs": 322,
+ "_ref": "7"
+ },
+ {
+ "name": "scanSampleProject",
+ "status": "OK",
+ "durationInMs": 184,
+ "_ref": "4"
+ },
+ {
+ "name": "computeMeasuresOnTempProject",
+ "status": "OK",
+ "durationInMs": 274,
+ "_ref": "2"
+ },
+ {
+ "name": "should_get_and_set_string_data",
+ "status": "OK",
+ "durationInMs": 95,
+ "_ref": "9"
+ }
+], "files": {
+ "3": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/index/CacheTest.java",
+ "longName": "src/test/java/org/sonar/batch/index/CacheTest.java"
+ },
+ "1": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/issue/IssueCacheTest.java",
+ "longName": "src/test/java/org/sonar/batch/issue/IssueCacheTest.java"
+ },
+ "4": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java",
+ "longName": "src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java"
+ },
+ "2": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java",
+ "longName": "src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java"
+ },
+ "8": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/index/ComponentDataPersisterTest.java",
+ "longName": "src/test/java/org/sonar/batch/index/ComponentDataPersisterTest.java"
+ },
+ "9": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/index/ComponentDataCacheTest.java",
+ "longName": "src/test/java/org/sonar/batch/index/ComponentDataCacheTest.java"
+ },
+ "10": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/scan/filesystem/InputFileCacheTest.java",
+ "longName": "src/test/java/org/sonar/batch/scan/filesystem/InputFileCacheTest.java"
+ },
+ "7": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java",
+ "longName": "src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java"
+ },
+ "5": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java",
+ "longName": "src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java"
+ },
+ "6": {
+ "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/index/CachesTest.java",
+ "longName": "src/test/java/org/sonar/batch/index/CachesTest.java"
+ }
+}}
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/app.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/app.json
new file mode 100644
index 00000000000..d2793968cb3
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/app.json
@@ -0,0 +1,4 @@
+{"key": "com.sonarsource.it.samples:sample-with-tests:src/test/java/sample/SampleTest.java", "path": "src/test/java/sample/SampleTest.java", "name": "SampleTest.java", "longName": "src/test/java/sample/SampleTest.java", "q": "UTS", "project": "com.sonarsource.it.samples:sample-with-tests", "projectName": "Sonar :: Integration Tests :: Sample with tests", "fav": false, "canMarkAsFavourite": false, "canBulkChange": false, "canCreateManualIssue": false, "periods": [], "severities": [], "rules": [], "measures": {
+ "fTests": "2",
+ "fIssues": "0"
+}}
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/covered-files.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/covered-files.json
new file mode 100644
index 00000000000..73f2e044078
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/covered-files.json
@@ -0,0 +1,7 @@
+{"files": [
+ {
+ "key": "com.sonarsource.it.samples:sample-with-tests:src/main/java/sample/Sample.java",
+ "longName": "src/main/java/sample/Sample.java",
+ "coveredLines": 5
+ }
+]}
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/resources.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/resources.json
new file mode 100644
index 00000000000..3490bce6f94
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/resources.json
@@ -0,0 +1,25 @@
+[
+ {
+ "id": 5,
+ "key": "com.sonarsource.it.samples:sample-with-tests:src/test/java/sample/SampleTest.java",
+ "name": "SampleTest.java",
+ "scope": "FIL",
+ "qualifier": "UTS",
+ "date": "2014-07-24T10:57:16+0200",
+ "creationDate": "2014-07-24T10:57:17+0200",
+ "lname": "src/test/java/sample/SampleTest.java",
+ "lang": "java",
+ "msr": [
+ {
+ "key": "tests",
+ "val": 2.0,
+ "frmt_val": "2"
+ },
+ {
+ "key": "test_execution_time",
+ "val": 12.0,
+ "frmt_val": "12 ms"
+ }
+ ]
+ }
+]
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/source.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/source.json
new file mode 100644
index 00000000000..02e9da776b2
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/source.json
@@ -0,0 +1,24 @@
+{"sources": [
+ [1, "<span class=\"k\">package</span> sample;"],
+ [2, ""],
+ [3, "<span class=\"k\">import</span> org.hamcrest.CoreMatchers;"],
+ [4, "<span class=\"k\">import</span> org.junit.Test;"],
+ [5, ""],
+ [6, "<span class=\"k\">import</span> <span class=\"k\">static</span> org.junit.Assert.assertThat;"],
+ [7, ""],
+ [8, "<span class=\"k\">public</span> <span class=\"k\">class</span> SampleTest {"],
+ [9, ""],
+ [10, " <span class=\"a\">@Test</span>"],
+ [11, " <span class=\"k\">public</span> <span class=\"k\">void</span> should_return_i() {"],
+ [12, " Sample sample = <span class=\"k\">new</span> Sample(1);"],
+ [13, " assertThat(sample.getI(), CoreMatchers.is(1));"],
+ [14, " }"],
+ [15, ""],
+ [16, " <span class=\"a\">@Test</span>"],
+ [17, " <span class=\"k\">public</span> <span class=\"k\">void</span> should_return_to_string() {"],
+ [18, " assertThat(<span class=\"k\">new</span> Sample(1).toString(), CoreMatchers.is(<span class=\"s\">\"1\"</span>));"],
+ [19, " }"],
+ [20, ""],
+ [21, "}"],
+ [22, ""]
+]}
diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/tests.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/tests.json
new file mode 100644
index 00000000000..f1ef789035c
--- /dev/null
+++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/tests.json
@@ -0,0 +1,14 @@
+{"tests": [
+ {
+ "name": "should_return_to_string",
+ "status": "OK",
+ "durationInMs": 1,
+ "coveredLines": 4
+ },
+ {
+ "name": "should_return_i",
+ "status": "OK",
+ "durationInMs": 11,
+ "coveredLines": 5
+ }
+]}
diff --git a/server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade b/server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade
index f00db74124a..6da4815d09a 100644
--- a/server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade
+++ b/server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade
@@ -9,7 +9,8 @@ html
var $j = jQuery.noConflict();
window.suppressTranslationWarnings = true;
jQuery.mockjaxSettings.contentType = 'text/json';
- jQuery.mockjaxSettings.responseTime = 250;
+ jQuery.mockjaxSettings.responseTime = 50;
+ jQuery.mockjaxSettings.throwUnmocked = true;
$j(document).ready(function () { $j('.open-modal').modal(); });
block header
body
diff --git a/server/sonar-web/src/main/less/component-viewer.less b/server/sonar-web/src/main/less/component-viewer.less
index 8ba6499c025..84ce9a28f63 100644
--- a/server/sonar-web/src/main/less/component-viewer.less
+++ b/server/sonar-web/src/main/less/component-viewer.less
@@ -70,6 +70,7 @@
white-space: nowrap;
&.active > a { font-weight: bold; }
+ .subtitle { line-height: 16px; }
}
.component-viewer-workspace-item + .component-viewer-workspace-item {
diff --git a/server/sonar-web/src/main/less/style.less b/server/sonar-web/src/main/less/style.less
index e7b6275252d..8a607cfa136 100644
--- a/server/sonar-web/src/main/less/style.less
+++ b/server/sonar-web/src/main/less/style.less
@@ -565,7 +565,7 @@ table.form td img {
height: 1px;
}
-table#project-history > tbody > tr > td {
+table#project-history tr > td {
vertical-align: top;
}
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 5cf89173487..934cb04c518 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
@@ -155,9 +155,9 @@
if update_center_activated %>
<li class="<%= 'active' if controller.controller_path=='updatecenter' -%>">
<a href="<%= ApplicationController.root_context -%>/updatecenter"><%= message('update_center.page') -%></a></li>
- <li class="<%= 'active' if controller.controller_path=='system' -%>">
- <a href="<%= ApplicationController.root_context -%>/system"><%= message('system_info.page') -%></a></li>
<% end %>
+ <li class="<%= 'active' if controller.controller_path=='system' -%>">
+ <a href="<%= ApplicationController.root_context -%>/system"><%= message('system_info.page') -%></a></li>
<% end #of admin part %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb
index 1815d10ee92..e582b6e2e12 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb
@@ -50,46 +50,56 @@
%>
<tr class="<%= cycle 'even','odd' -%> hoverable snapshot">
+ <%# Year %>
<td class="thin nowrap"><b><%= time.year unless time.year == current_year -%></b></td>
+ <%# Month %>
<td class="thin nowrap"><b><%= l(time, :format => '%B').capitalize unless time.month == current_month -%></b></td>
+ <%# Day %>
<td class="thin nowrap"><%= l(time, :format => '%d') -%></td>
+ <%# Time %>
<td class="thin nowrap"><%= l(time, :format => '%H:%M') -%></td>
+ <%# Version %>
<td class="thin nowrap" style="padding-left: 20px;">
- <table class="width100">
- <tr id="version_<%= index -%>">
- <% if version_event %>
- <td class="width100"><%= version_event.name if version_event -%></td>
- <td class="small edit_actions" style="padding-left:20px">
- <a id="version_<%= index -%>_change" href="#" onclick="$j('#version_<%= index -%>').hide();$j('#version_<%= index -%>_form').show();$j('#version_name_<%= index -%>').focus();return false;"><%= message('project_history.rename_version') -%></a>
- <% if version_event && !snapshot.islast? %>
- <%= link_to( message('project_history.remove_version'),
- { :action => 'delete_version', :sid => snapshot.id},
- :confirm => message('project_history.do_you_want_to_remove_version', :params => version_event.name) ) -%>
- <% end %>
- </td>
- <% else %>
- <td class="small edit_actions" colspan="2">
- <a id="version_<%= index -%>_change" href="#" onclick="$j('#version_<%= index -%>').hide();$j('#version_<%= index -%>_form').show();$j('#version_name_<%= index -%>').focus();return false;"><%= message('project_history.create_version') -%></a>
- </td>
+ <table class="width100">
+ <tr id="version_<%= index -%>">
+
+ <% if version_event %>
+ <td class="width100"><%= version_event.name if version_event -%></td>
+ <td class="small edit_actions" style="padding-left:20px">
+ <a id="version_<%= index -%>_change" href="#" onclick="$j('#version_<%= index -%>').hide();$j('#version_<%= index -%>_form').show();$j('#version_name_<%= index -%>').focus();return false;"><%= message('project_history.rename_version') -%></a>
+
+ <% if version_event && !snapshot.islast? %>
+ <%= link_to( message('project_history.remove_version'),
+ { :action => 'delete_version', :sid => snapshot.id},
+ :confirm => message('project_history.do_you_want_to_remove_version', :params => version_event.name) ) -%>
<% end %>
- </tr>
- <tr id="version_<%= index -%>_form" style="display:none;">
- <td coslpan="2" class="admin">
- <% form_tag( {:action => 'update_version', :sid => snapshot.id }) do -%>
- <input id="version_name_<%= index -%>" name="version_name" type="text" value="<%= version_event ? version_event.name : '' -%>"
- onKeyUp="if (this.value=='') $j('#save_version_<%= index -%>').disabled='true'; else $j('#save_version_<%= index -%>').disabled='';"/>
- <%= submit_tag message('save'), :id => 'save_version_' + index.to_s %>
- <a href="#" onclick="$j('#version_<%= index -%>').show();$j('#version_<%= index -%>_form').hide();"><%= message('cancel') -%></a>
- <% end %>
- </td>
- </tr>
- </table>
+ </td>
+
+ <% else %>
+ <td class="small edit_actions" colspan="2">
+ <a id="version_<%= index -%>_change" href="#" onclick="$j('#version_<%= index -%>').hide();$j('#version_<%= index -%>_form').show();$j('#version_name_<%= index -%>').focus();return false;"><%= message('project_history.create_version') -%></a>
+ </td>
+
+ <% end %>
+ </tr>
+ <tr id="version_<%= index -%>_form" style="display:none;">
+ <td coslpan="2" class="admin">
+ <% form_tag( {:action => 'update_version', :sid => snapshot.id }) do -%>
+ <input id="version_name_<%= index -%>" name="version_name" type="text" value="<%= version_event ? version_event.name : '' -%>"
+ onKeyUp="if (this.value=='') $j('#save_version_<%= index -%>').disabled='true'; else $j('#save_version_<%= index -%>').disabled='';"/>
+ <%= submit_tag message('save'), :id => 'save_version_' + index.to_s %>
+ <a href="#" onclick="$j('#version_<%= index -%>').show();$j('#version_<%= index -%>_form').hide();"><%= message('cancel') -%></a>
+ <% end %>
+ </td>
+ </tr>
+ </table>
</td>
+ <%# Events %>
<td class="thin nowrap" style="padding-left: 20px;">
<table class="width100">
<%
@@ -99,28 +109,26 @@
<tr id="event_<%= event_index -%>">
<td class="width100"><%= event.name -%></td>
<td class="small edit_actions" style="padding-left:20px">
- <a id="event_<%= event_index -%>_change" href="#" onclick="$j('#event_<%= event_index -%>').hide();$j('#event_<%= event_index -%>_form').show();$j('#event_name_<%= event_index -%>').focus();return false;"><%= message('project_history.rename_event') -%></a>
- <%= link_to( message('project_history.remove_version'),
- { :action => 'delete_event', :id => event.id},
- :confirm => message('project_history.do_you_want_to_remove_version', :params => event.name) ) -%>
+ <a id="event_<%= event_index -%>_change" href="#" onclick="$j('#event_<%= event_index -%>').hide();$j('#event_<%= event_index -%>_form').show();$j('#event_name_<%= event_index -%>').focus();return false;"><%= message('project_history.rename_event') -%></a>
+ <%= link_to( message('project_history.remove_version'),
+ { :action => 'delete_event', :id => event.id},
+ :confirm => message('project_history.do_you_want_to_remove_version', :params => event.name) ) -%>
</td>
</tr>
<tr id="event_<%= event_index -%>_form" style="display:none;">
- <td colspan="2" class="admin">
- <% form_tag( {:action => 'update_event', :id => event.id }) do -%>
- <input id="event_name_<%= event_index -%>" name="event_name" type="text" value="<%= event.name -%>"
- onKeyUp="if (this.value=='') $j('#save_event_<%= event_index -%>').disabled='true'; else $j('#save_event_<%= event_index -%>').disabled='';"/>
- <%= submit_tag message('save'), :id => 'save_event_' + event_index %>
- <a href="#" onclick="$j('#event_<%= event_index -%>').show();$j('#event_<%= event_index -%>_form').hide();"><%= message('cancel') -%></a>
- <% end %>
- </td>
+ <td colspan="2" class="admin">
+ <% form_tag( {:action => 'update_event', :id => event.id }) do -%>
+ <input id="event_name_<%= event_index -%>" name="event_name" type="text" value="<%= event.name -%>"
+ onKeyUp="if (this.value=='') $j('#save_event_<%= event_index -%>').disabled='true'; else $j('#save_event_<%= event_index -%>').disabled='';"/>
+ <%= submit_tag message('save'), :id => 'save_event_' + event_index %>
+ <a href="#" onclick="$j('#event_<%= event_index -%>').show();$j('#event_<%= event_index -%>_form').hide();"><%= message('cancel') -%></a>
+ <% end %>
+ </td>
</tr>
<% end %>
<tr id="create_event_<%= index -%>">
- <td colspan="2" class="create_actions">
- <span class="small">
- <a id="create_event_<%= index -%>_change" href="#" onclick="$j('#create_event_<%= index -%>').hide();$j('#create_event_<%= index -%>_form').show();$j('#create_event_name_<%= index -%>').focus();return false;"><%= message('project_history.create_event') -%></a>
- </span>
+ <td colspan="2" class="small create_actions">
+ <a id="create_event_<%= index -%>_change" href="#" onclick="$j('#create_event_<%= index -%>').hide();$j('#create_event_<%= index -%>_form').show();$j('#create_event_name_<%= index -%>').focus();return false;"><%= message('project_history.create_event') -%></a>
</td>
</tr>
<tr id="create_event_<%= index -%>_form" style="display:none;">