aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsimonbrandhof <simon.brandhof@gmail.com>2011-05-19 16:14:21 +0200
committersimonbrandhof <simon.brandhof@gmail.com>2011-05-19 16:14:21 +0200
commitff3117718a1642e99d97416b88b2db702d1b96f8 (patch)
treeaa3afbd405482ee72453b9dca17266b5465d8e66
parent1dfe5d781675f0a7dfe19dce93757a47262abbae (diff)
downloadsonarqube-ff3117718a1642e99d97416b88b2db702d1b96f8.tar.gz
sonarqube-ff3117718a1642e99d97416b88b2db702d1b96f8.zip
Fix merge of release 2.8
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java31
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest.java28
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest/shouldCloseReviewWithoutCorrespondingViolation-result.xml6
-rw-r--r--plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java35
-rw-r--r--plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviews.java66
-rw-r--r--plugins/sonar-dbcleaner-plugin/src/test/java/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest.java46
-rw-r--r--plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest/purgeOrphanReviews-result.xml43
-rw-r--r--plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest/purgeOrphanReviews.xml39
-rw-r--r--samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/SamplePlugin.java4
-rw-r--r--samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/rules/SampleQualityProfile.java27
-rw-r--r--samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/rules/SampleRuleRepository.java39
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java2
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java39
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/reviews_controller.rb10
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/project.rb12
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/review.rb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_violation.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_review.html.erb3
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_show.html.erb11
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/reviews/index.html.erb6
-rw-r--r--sonar-server/src/main/webapp/stylesheets/style.css17
-rw-r--r--sonar-testing-harness/src/main/java/org/sonar/test/persistence/DatabaseTestCase.java14
-rw-r--r--sonar-testing-harness/src/main/resources/org/sonar/test/persistence/sonar-test.ddl1
24 files changed, 338 insertions, 147 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java
index b46108b8840..0b32e50b288 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CloseReviewsDecorator.java
@@ -19,6 +19,8 @@
*/
package org.sonar.plugins.core.sensors;
+import javax.persistence.Query;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.Decorator;
@@ -29,10 +31,9 @@ import org.sonar.api.database.DatabaseSession;
import org.sonar.api.database.model.Snapshot;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
import org.sonar.batch.index.ResourcePersister;
-import javax.persistence.Query;
-
/**
* Decorator that currently only closes a review when its corresponding violation has been fixed.
*/
@@ -50,7 +51,7 @@ public class CloseReviewsDecorator implements Decorator {
}
public boolean shouldExecuteOnProject(Project project) {
- return true;
+ return project.isLatestAnalysis();
}
public void decorate(Resource resource, DecoratorContext context) {
@@ -58,16 +59,32 @@ public class CloseReviewsDecorator implements Decorator {
if (currentSnapshot != null) {
int resourceId = currentSnapshot.getResourceId();
int snapshotId = currentSnapshot.getId();
- Query query = databaseSession.createNativeQuery(generateSqlRequest(resourceId, snapshotId));
+ Query query = databaseSession.createNativeQuery(generateUpdateOnResourceSqlRequest(resourceId, snapshotId));
int rowUpdated = query.executeUpdate();
LOG.debug("- {} reviews set to 'closed' on resource #{}", rowUpdated, resourceId);
+
+ if (ResourceUtils.isRootProject(resource)) {
+ query = databaseSession.createNativeQuery(generateUpdateOnProjectSqlRequest(resourceId, currentSnapshot.getId()));
+ query.setParameter(1, Boolean.TRUE);
+ rowUpdated = query.executeUpdate();
+ LOG.debug("- {} reviews set to 'closed' on project #{}", rowUpdated, resourceId);
+ }
+
databaseSession.commit();
}
}
- String generateSqlRequest(int resourceId, int snapshotId) {
- return "UPDATE reviews SET status='CLOSED' WHERE resource_id = " + resourceId + " AND rule_failure_permanent_id NOT IN "
- + "(SELECT permanent_id FROM rule_failures WHERE snapshot_id = " + snapshotId + " AND permanent_id IS NOT NULL)";
+ protected String generateUpdateOnResourceSqlRequest(int resourceId, int snapshotId) {
+ return "UPDATE reviews SET status='CLOSED', updated_at=CURRENT_TIMESTAMP WHERE resource_id = " + resourceId
+ + " AND rule_failure_permanent_id NOT IN " + "(SELECT permanent_id FROM rule_failures WHERE snapshot_id = " + snapshotId
+ + " AND permanent_id IS NOT NULL)";
+ }
+
+ protected String generateUpdateOnProjectSqlRequest(int projectId, int projectSnapshotId) {
+ return "UPDATE reviews SET status='CLOSED', updated_at=CURRENT_TIMESTAMP WHERE status='OPEN' AND project_id=" + projectId
+ + " AND resource_id IN ( SELECT prev.project_id FROM snapshots prev WHERE prev.root_project_id=" + projectId
+ + " AND prev.islast=? AND NOT EXISTS ( SELECT cur.id FROM snapshots cur WHERE cur.root_snapshot_id=" + projectSnapshotId
+ + " AND cur.created_at > prev.created_at AND cur.root_project_id=" + projectId + " AND cur.project_id=prev.project_id ) )";
}
}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest.java
index 493352c1147..2ea8a7c1de4 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest.java
@@ -21,25 +21,47 @@ package org.sonar.plugins.core.sensors;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.sql.Statement;
+import junit.framework.ComparisonFailure;
+
import org.junit.Test;
+import org.sonar.api.resources.Project;
import org.sonar.test.persistence.DatabaseTestCase;
public class CloseReviewsDecoratorTest extends DatabaseTestCase {
@Test
+ public void testShouldExecuteOnProject() throws Exception {
+ Project project = mock(Project.class);
+ when(project.isLatestAnalysis()).thenReturn(true);
+ CloseReviewsDecorator reviewsDecorator = new CloseReviewsDecorator(null, null);
+ assertTrue(reviewsDecorator.shouldExecuteOnProject(project));
+ }
+
+ @Test
public void shouldCloseReviewWithoutCorrespondingViolation() throws Exception {
setupData("fixture");
CloseReviewsDecorator reviewsDecorator = new CloseReviewsDecorator(null, null);
- String sqlRequest = reviewsDecorator.generateSqlRequest(666, 222);
-
+ String sqlRequest = reviewsDecorator.generateUpdateOnResourceSqlRequest(666, 222);
+
Statement stmt = getConnection().createStatement();
int count = stmt.executeUpdate(sqlRequest);
assertThat(count, is(1));
- assertTables("shouldCloseReviewWithoutCorrespondingViolation", "reviews");
+ assertTables("shouldCloseReviewWithoutCorrespondingViolation", new String[] { "reviews" }, new String[] { "updated_at" });
+
+ try {
+ assertTables("shouldCloseReviewWithoutCorrespondingViolation", new String[] { "reviews" });
+ fail("'updated_at' columns are identical whereas they should be different.");
+ } catch (ComparisonFailure e) {
+ // "updated_at" column must be different, so the comparison should raise this exception
+ }
}
}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest/shouldCloseReviewWithoutCorrespondingViolation-result.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest/shouldCloseReviewWithoutCorrespondingViolation-result.xml
index d46429ddf3d..db7bf76f0e0 100644
--- a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest/shouldCloseReviewWithoutCorrespondingViolation-result.xml
+++ b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/sensors/CloseReviewsDecoratorTest/shouldCloseReviewWithoutCorrespondingViolation-result.xml
@@ -5,18 +5,18 @@
status="OPEN"
rule_failure_permanent_id="1"
resource_id="555"
- created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]"/>
+ created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]" project_id="[null]"/>
<reviews
id="2"
status="CLOSED"
rule_failure_permanent_id="2"
resource_id="666"
- created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]"/>
+ created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]" project_id="[null]"/>
<reviews
id="3"
status="OPEN"
rule_failure_permanent_id="3"
resource_id="666"
- created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]"/>
+ created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]" project_id="[null]"/>
</dataset> \ No newline at end of file
diff --git a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java
index f38a6c39b20..a02d8d0a308 100644
--- a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java
+++ b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/DbCleanerPlugin.java
@@ -19,30 +19,41 @@
*/
package org.sonar.plugins.dbcleaner;
+import java.util.Arrays;
+import java.util.List;
+
import org.sonar.api.Properties;
import org.sonar.api.Property;
import org.sonar.api.SonarPlugin;
import org.sonar.plugins.dbcleaner.api.DbCleanerConstants;
import org.sonar.plugins.dbcleaner.period.DefaultPeriodCleaner;
import org.sonar.plugins.dbcleaner.period.PeriodPurge;
-import org.sonar.plugins.dbcleaner.purges.*;
+import org.sonar.plugins.dbcleaner.purges.PurgeDeletedResources;
+import org.sonar.plugins.dbcleaner.purges.PurgeDependencies;
+import org.sonar.plugins.dbcleaner.purges.PurgeDeprecatedLast;
+import org.sonar.plugins.dbcleaner.purges.PurgeDisabledResources;
+import org.sonar.plugins.dbcleaner.purges.PurgeEntities;
+import org.sonar.plugins.dbcleaner.purges.PurgeEventOrphans;
+import org.sonar.plugins.dbcleaner.purges.PurgeOrphanResources;
+import org.sonar.plugins.dbcleaner.purges.PurgeOrphanReviews;
+import org.sonar.plugins.dbcleaner.purges.PurgePropertyOrphans;
+import org.sonar.plugins.dbcleaner.purges.PurgeResourceRoles;
+import org.sonar.plugins.dbcleaner.purges.PurgeRuleMeasures;
+import org.sonar.plugins.dbcleaner.purges.PurgeUnprocessed;
import org.sonar.plugins.dbcleaner.runner.PurgeRunner;
-import java.util.Arrays;
-import java.util.List;
-
@Properties({
- @Property(key = DbCleanerConstants.MONTHS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_WEEK,
- defaultValue = DbCleanerConstants.ONE_MONTH, name = "Number of months before starting to keep only one snapshot by week",
+ @Property(key = DbCleanerConstants.MONTHS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_WEEK, defaultValue = DbCleanerConstants.ONE_MONTH,
+ name = "Number of months before starting to keep only one snapshot by week",
description = "After this number of months, if there are several snapshots during the same week, "
+ "the DbCleaner keeps the first one and fully delete the other ones.", global = true, project = true),
- @Property(key = DbCleanerConstants.MONTHS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_MONTH,
- defaultValue = DbCleanerConstants.ONE_YEAR, name = "Number of months before starting to keep only one snapshot by month",
+ @Property(key = DbCleanerConstants.MONTHS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_MONTH, defaultValue = DbCleanerConstants.ONE_YEAR,
+ name = "Number of months before starting to keep only one snapshot by month",
description = "After this number of months, if there are several snapshots during the same month, "
+ "the DbCleaner keeps the first one and fully delete the other ones.", global = true, project = true),
@Property(key = DbCleanerConstants.MONTHS_BEFORE_DELETING_ALL_SNAPSHOTS, defaultValue = DbCleanerConstants.FIVE_YEARS,
name = "Number of months before starting to delete all remaining snapshots",
- description = "After this number of months, all snapshots are fully deleted.", global = true, project = true)})
+ description = "After this number of months, all snapshots are fully deleted.", global = true, project = true) })
public final class DbCleanerPlugin extends SonarPlugin {
public List getExtensions() {
@@ -51,9 +62,9 @@ public final class DbCleanerPlugin extends SonarPlugin {
DefaultPeriodCleaner.class,
// purges
- PurgeOrphanResources.class, PurgeEntities.class, PurgeRuleMeasures.class, PurgeUnprocessed.class,
- PurgeDeletedResources.class, PurgeDeprecatedLast.class, PurgeDisabledResources.class,
- PurgeResourceRoles.class, PurgeEventOrphans.class, PurgePropertyOrphans.class, PeriodPurge.class, PurgeDependencies.class,
+ PurgeOrphanResources.class, PurgeEntities.class, PurgeRuleMeasures.class, PurgeUnprocessed.class, PurgeDeletedResources.class,
+ PurgeDeprecatedLast.class, PurgeDisabledResources.class, PurgeResourceRoles.class, PurgeEventOrphans.class,
+ PurgePropertyOrphans.class, PeriodPurge.class, PurgeDependencies.class, PurgeOrphanReviews.class,
// post-job
PurgeRunner.class);
diff --git a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviews.java b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviews.java
new file mode 100644
index 00000000000..8e52416b767
--- /dev/null
+++ b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviews.java
@@ -0,0 +1,66 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.dbcleaner.purges;
+
+import javax.persistence.Query;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.plugins.dbcleaner.api.Purge;
+import org.sonar.plugins.dbcleaner.api.PurgeContext;
+
+/**
+ * Purge Review that are attached to projects that have been deleted.
+ *
+ * @since 2.8
+ */
+public final class PurgeOrphanReviews extends Purge {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PurgeOrphanReviews.class);
+
+ public PurgeOrphanReviews(DatabaseSession session) {
+ super(session);
+ }
+
+ public void purge(PurgeContext context) {
+ DatabaseSession session = getSession();
+
+ // delete reviews
+ Query query = session.createNativeQuery(getDeleteReviewsSqlRequest());
+ int rowDeleted = query.executeUpdate();
+ LOG.debug("- {} reviews deleted.", rowDeleted);
+
+ // and delete review comments
+ query = session.createNativeQuery(getDeleteReviewCommentsSqlRequest());
+ rowDeleted = query.executeUpdate();
+ LOG.debug("- {} review comments deleted.", rowDeleted);
+
+ session.commit();
+ }
+
+ protected String getDeleteReviewsSqlRequest() {
+ return "DELETE FROM reviews WHERE project_id not in (SELECT id FROM projects WHERE scope = 'PRJ' and qualifier = 'TRK')";
+ }
+
+ protected String getDeleteReviewCommentsSqlRequest() {
+ return "DELETE FROM review_comments WHERE review_id not in (SELECT id FROM reviews)";
+ }
+}
diff --git a/plugins/sonar-dbcleaner-plugin/src/test/java/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest.java b/plugins/sonar-dbcleaner-plugin/src/test/java/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest.java
new file mode 100644
index 00000000000..de1632ac757
--- /dev/null
+++ b/plugins/sonar-dbcleaner-plugin/src/test/java/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest.java
@@ -0,0 +1,46 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.dbcleaner.purges;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.sql.Statement;
+
+import org.junit.Test;
+import org.sonar.test.persistence.DatabaseTestCase;
+
+public class PurgeOrphanReviewsTest extends DatabaseTestCase {
+
+ @Test
+ public void shouldCloseReviewWithoutCorrespondingViolation() throws Exception {
+ setupData("purgeOrphanReviews");
+
+ Statement stmt = getConnection().createStatement();
+ int count = stmt.executeUpdate(new PurgeOrphanReviews(null).getDeleteReviewsSqlRequest());
+ assertThat(count, is(1));
+
+ count = stmt.executeUpdate(new PurgeOrphanReviews(null).getDeleteReviewCommentsSqlRequest());
+ assertThat(count, is(1));
+
+ assertTables("purgeOrphanReviews", "reviews");
+ }
+
+}
diff --git a/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest/purgeOrphanReviews-result.xml b/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest/purgeOrphanReviews-result.xml
new file mode 100644
index 00000000000..7680bdd39c0
--- /dev/null
+++ b/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest/purgeOrphanReviews-result.xml
@@ -0,0 +1,43 @@
+<dataset>
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="other-project" name="other-project"
+ root_id="[null]" profile_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- project1 has been removed from UI -->
+ <!--<projects long_name="[null]" id="2" scope="PRJ" qualifier="TRK" kee="project1" name="project1"-->
+ <!--root_id="[null]"-->
+ <!--description="[null]"-->
+ <!--enabled="true" language="java" copy_resource_id="[null]"/>-->
+
+ <reviews
+ id="1"
+ status="OPEN"
+ rule_failure_permanent_id="1"
+ resource_id="555"
+ project_id="1"
+ created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]"/>
+
+ <!-- Following must have been deleted
+ <reviews
+ id="2"
+ status="CLOSED"
+ rule_failure_permanent_id="2"
+ resource_id="666"
+ project_id="2"
+ created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]"/>
+ -->
+
+ <review_comments
+ id="1"
+ review_id="1"
+ created_at="[null]" updated_at="[null]" user_id="[null]" review_text=""/>
+
+ <!-- Following must have been deleted
+ <review_comments
+ id="2"
+ review_id="2"
+ created_at="[null]" updated_at="[null]" user_id="[null]" review_text=""/>
+ -->
+
+</dataset> \ No newline at end of file
diff --git a/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest/purgeOrphanReviews.xml b/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest/purgeOrphanReviews.xml
new file mode 100644
index 00000000000..01da1669b40
--- /dev/null
+++ b/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/purges/PurgeOrphanReviewsTest/purgeOrphanReviews.xml
@@ -0,0 +1,39 @@
+<dataset>
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="other-project" name="other-project"
+ root_id="[null]" profile_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- project1 has been removed from UI -->
+ <!--<projects long_name="[null]" id="2" scope="PRJ" qualifier="TRK" kee="project1" name="project1"-->
+ <!--root_id="[null]"-->
+ <!--description="[null]"-->
+ <!--enabled="true" language="java" copy_resource_id="[null]"/>-->
+
+ <reviews
+ id="1"
+ status="OPEN"
+ rule_failure_permanent_id="1"
+ resource_id="555"
+ project_id="1"
+ created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]"/>
+ <reviews
+ id="2"
+ status="CLOSED"
+ rule_failure_permanent_id="2"
+ resource_id="666"
+ project_id="2"
+ created_at="[null]" updated_at="[null]" user_id="[null]" assignee_id="[null]" title="[null]" review_type="[null]" severity="[null]" resource_line="[null]"/>
+
+ <review_comments
+ id="1"
+ review_id="1"
+ created_at="[null]" updated_at="[null]" user_id="[null]" review_text=""/>
+
+ <review_comments
+ id="2"
+ review_id="2"
+ created_at="[null]" updated_at="[null]" user_id="[null]" review_text=""/>
+
+
+</dataset> \ No newline at end of file
diff --git a/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/SamplePlugin.java b/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/SamplePlugin.java
index f530e6b38b4..7622e032bd0 100644
--- a/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/SamplePlugin.java
+++ b/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/SamplePlugin.java
@@ -2,8 +2,6 @@ package com.mycompany.sonar.standard;
import com.mycompany.sonar.standard.batch.RandomDecorator;
import com.mycompany.sonar.standard.batch.SampleSensor;
-import com.mycompany.sonar.standard.rules.SampleQualityProfile;
-import com.mycompany.sonar.standard.rules.SampleRuleRepository;
import com.mycompany.sonar.standard.ui.SampleFooter;
import com.mycompany.sonar.standard.ui.SampleRubyWidget;
import org.sonar.api.SonarPlugin;
@@ -20,7 +18,7 @@ public final class SamplePlugin extends SonarPlugin {
public List getExtensions() {
return Arrays.asList(
// Definitions
- SampleRuleRepository.class, SampleMetrics.class, SampleQualityProfile.class,
+ SampleMetrics.class,
// Batch
SampleSensor.class, RandomDecorator.class,
diff --git a/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/rules/SampleQualityProfile.java b/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/rules/SampleQualityProfile.java
deleted file mode 100644
index ac6e981085b..00000000000
--- a/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/rules/SampleQualityProfile.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.mycompany.sonar.standard.rules;
-
-import org.sonar.api.profiles.ProfileDefinition;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.Java;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.utils.ValidationMessages;
-
-public class SampleQualityProfile extends ProfileDefinition {
-
- private SampleRuleRepository ruleRepository;
-
- // The component ruleRepository is injected at runtime
- public SampleQualityProfile(SampleRuleRepository ruleRepository) {
- this.ruleRepository = ruleRepository;
- }
-
- @Override
- public RulesProfile createProfile(ValidationMessages validation) {
- RulesProfile profile = RulesProfile.create("Sample profile", Java.KEY);
- profile.activateRule(ruleRepository.getRule1(), RulePriority.MAJOR);
- profile.activateRule(ruleRepository.getRule2(), null);
- profile.activateRule(Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.coding.CovariantEqualsCheck", null), null);
- return profile;
- }
-}
diff --git a/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/rules/SampleRuleRepository.java b/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/rules/SampleRuleRepository.java
deleted file mode 100644
index 4bd909a3b11..00000000000
--- a/samples/standard-plugin/src/main/java/com/mycompany/sonar/standard/rules/SampleRuleRepository.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.mycompany.sonar.standard.rules;
-
-import org.sonar.api.resources.Java;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.rules.RuleRepository;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * This class declares rules. It is not the engine used to execute rules during project analysis.
- */
-public class SampleRuleRepository extends RuleRepository {
-
- public static final String REPOSITORY_KEY = "sample";
-
- public SampleRuleRepository() {
- super(REPOSITORY_KEY, Java.KEY);
- setName("Sample");
- }
-
- @Override
- public List<Rule> createRules() {
- // This method is called when server is started. It's used to register rules into database.
- // Definition of rules can be loaded from any sources, like XML files.
- return Arrays.asList(
- getRule1(),
- getRule2());
- }
-
- public Rule getRule1() {
- return Rule.create(REPOSITORY_KEY, "fake1", "Fake one").setSeverity(RulePriority.CRITICAL);
- }
-
- public Rule getRule2() {
- return Rule.create(REPOSITORY_KEY, "fake2", "Fake two").setDescription("Description of fake two");
- }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java b/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java
index 7a6ea61b454..2216a49c1b5 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java
@@ -104,7 +104,7 @@ public class ProjectBatch {
@Override
protected void configure() {
- ProjectDefinition projectDefinition = getComponent(ProjectTree.class).getProjectDefinition(project.getKey());
+ ProjectDefinition projectDefinition = getComponent(ProjectTree.class).getProjectDefinition(project);
addComponent(projectDefinition);
for (Object component : projectDefinition.getContainerExtensions()) {
addComponent(component);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java b/sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java
index 54b97fb2b6f..1ea52bc5738 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java
@@ -21,10 +21,10 @@ package org.sonar.batch;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.project.MavenProject;
import org.slf4j.LoggerFactory;
-import org.sonar.api.CoreProperties;
import org.sonar.api.database.DatabaseSession;
import org.sonar.api.resources.Project;
import org.sonar.batch.bootstrapper.ProjectDefinition;
@@ -35,9 +35,10 @@ import java.util.*;
public class ProjectTree {
- private List<Project> projects;
private ProjectBuilder projectBuilder;
+ private List<Project> projects;
private List<ProjectDefinition> definitions;
+ private Map<ProjectDefinition, Project> projectsMap;
public ProjectTree(Reactor sonarReactor, DatabaseSession databaseSession) {
this.projectBuilder = new ProjectBuilder(databaseSession);
@@ -75,24 +76,24 @@ public class ProjectTree {
public void start() throws IOException {
projects = Lists.newArrayList();
- Map<ProjectDefinition, Project> map = Maps.newHashMap();
+ projectsMap = Maps.newHashMap();
for (ProjectDefinition def : definitions) {
Project project = projectBuilder.create(def);
- map.put(def, project);
+ projectsMap.put(def, project);
projects.add(project);
}
- for (Map.Entry<ProjectDefinition, Project> entry : map.entrySet()) {
+ for (Map.Entry<ProjectDefinition, Project> entry : projectsMap.entrySet()) {
ProjectDefinition def = entry.getKey();
Project project = entry.getValue();
for (ProjectDefinition module : def.getModules()) {
- map.get(module).setParent(project);
+ projectsMap.get(module).setParent(project);
}
}
// Configure
- for (Map.Entry<ProjectDefinition, Project> entry : map.entrySet()) {
+ for (Map.Entry<ProjectDefinition, Project> entry : projectsMap.entrySet()) {
projectBuilder.configure(entry.getValue(), entry.getKey());
}
@@ -181,26 +182,12 @@ public class ProjectTree {
throw new IllegalStateException("Can not find the root project from the list of Maven modules");
}
- private String getProjectKey(ProjectDefinition def) {
- String key = def.getProperties().getProperty(CoreProperties.PROJECT_KEY_PROPERTY);
- String branch = def.getProperties().getProperty(CoreProperties.PROJECT_BRANCH_PROPERTY);
- if (StringUtils.isNotBlank(branch)) {
- return key + ":" + branch;
- } else {
- return key;
- }
- }
-
- public ProjectDefinition getProjectDefinition(String key) {
- for (ProjectDefinition def : definitions) {
- if (StringUtils.equals(key, getProjectKey(def))) {
- return def;
+ public ProjectDefinition getProjectDefinition(Project project) {
+ for (Map.Entry<ProjectDefinition, Project> entry : projectsMap.entrySet()) {
+ if (ObjectUtils.equals(entry.getValue(), project)) {
+ return entry.getKey();
}
}
- return null;
- }
-
- public List<Object> getProjectExtensions(String key) {
- return getProjectDefinition(key).getContainerExtensions();
+ throw new IllegalStateException("Can not find ProjectDefinition for " + project);
}
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/reviews_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/reviews_controller.rb
index 62b3f3b8759..7a69b96676c 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/reviews_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/reviews_controller.rb
@@ -53,7 +53,7 @@ class ReviewsController < ApplicationController
def show
@review = Review.find(params[:id], :include => ['project'])
if has_role?(:user, @review.project)
- render :partial => 'reviews/view'
+ render :partial => 'reviews/show'
else
render :text => "access denied"
end
@@ -76,7 +76,7 @@ class ReviewsController < ApplicationController
@review.assignee = User.find params[:assignee_id]
@review.save
- render :partial => 'reviews/view'
+ render :partial => 'reviews/show'
end
# GET
@@ -106,7 +106,7 @@ class ReviewsController < ApplicationController
@review.comments.create!(:user => current_user, :text => params[:text])
end
- render :partial => "reviews/view"
+ render :partial => "reviews/show"
end
# GET
@@ -134,7 +134,7 @@ class ReviewsController < ApplicationController
@review.comments.create(:review_text => params[:comment], :user_id => current_user.id)
end
- render :partial => "reviews/view"
+ render :partial => "reviews/show"
end
# POST
@@ -149,7 +149,7 @@ class ReviewsController < ApplicationController
comment=@review.comments.find(params[:comment_id].to_i)
comment.delete if comment
end
- render :partial => "reviews/view"
+ render :partial => "reviews/show"
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb
index e7f95603788..854c5f746db 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb
@@ -32,6 +32,8 @@ class SettingsController < ApplicationController
project=Project.by_key(params[:resource_id])
return access_denied unless is_admin?(project)
resource_id=project.id
+ else
+ return access_denied unless is_admin?
end
plugins = java_facade.getPluginsMetadata()
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb
index 8489caea88b..9658c9dfcb3 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb
@@ -43,6 +43,13 @@ class Project < ActiveRecord::Base
root||self
end
+ def root_project
+ @root_project ||=
+ begin
+ parent_module(self)
+ end
+ end
+
def last_snapshot
@last_snapshot ||=
begin
@@ -153,4 +160,9 @@ class Project < ActiveRecord::Base
end
chart_measures
end
+
+ def parent_module(current_module)
+ current_module.root ? parent_module(current_module.root) : current_module
+ end
+
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/review.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/review.rb
index 46e97ba9c95..1f91e4abe79 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/review.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/review.rb
@@ -205,7 +205,7 @@ class Review < ActiveRecord::Base
def assign_project
if self.project.nil? && self.resource
- self.project=self.resource.project
+ self.project=self.resource.root_project
end
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_violation.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_violation.html.erb
index 25989a7b1e0..3282a0fedee 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_violation.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_violation.html.erb
@@ -2,7 +2,7 @@
<div class="violation">
<div class="vtitle">
<% if violation.review %>
- <div class="review_permalink"><span class="review_permalink"><%= link_to "Review #"+violation.review.id.to_s, :controller => "reviews", :action => "view", :id => violation.review.id -%></span></div>
+ <div style="float: right"><span class="review_permalink"><%= link_to violation.review.id.to_s, :controller => "reviews", :action => "view", :id => violation.review.id -%></span></div>
<% end %>
<%= image_tag("priority/" + violation.failure_level.to_s + '.png') -%>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_review.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_review.html.erb
index 5e8fcd7299e..79b0a2c864d 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_review.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_review.html.erb
@@ -1,6 +1,7 @@
<div id="rev_<%= review.id -%>">
<div class="reportTitle">
- <h2>Review #<%= h(review.id.to_s) -%> - <%= h(review.title) -%></h2>
+ <div style="float: right"><span class="violation_date"><%= review.id.to_s -%></span></div>
+ <h2><%= h(review.title) -%></h2>
<% if current_user && review.status != "CLOSED" %>
<span class="actions" id="rActions">
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_show.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_show.html.erb
new file mode 100644
index 00000000000..b9faf2e6d30
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_show.html.erb
@@ -0,0 +1,11 @@
+<div id="backReviewDiv" class="marginbottom10">
+ <a href="#" onclick="backReviews()">&laquo; Back to reviews</a>
+</div>
+<script>
+ if ($('reviews-search')==null) {
+ // This happens when this page results from a call made from the review permalink page
+ $('backReviewDiv').hide();
+ }
+</script>
+
+<%= render :partial => 'reviews/review', :locals => {:review => @review} -%> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/index.html.erb
index 9946a4564f8..7bd4895f05a 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/index.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/index.html.erb
@@ -107,11 +107,11 @@ function reviewIdFieldModified(field) {
<tr class="<%= cycle('even', 'odd') -%>">
<td><img src="<%= ApplicationController.root_context -%>/images/status/<%= review.status -%>.png" title="<%= review.status.capitalize -%>"/></td>
<td>
- <%= link_to h(review.id), :controller => "reviews", :action => "view", :id => review.id -%>
+ <%= link_to_remote( h(review.id), :update => 'review', :url => {:action => 'show', :id => review.id}, :loading => 'onReviewLoading()', :complete => "onReviewLoaded()") -%>
</td>
<td><img src="<%= ApplicationController.root_context -%>/images/priority/<%= review.severity -%>.png" title="<%= review.severity.capitalize -%>"/></td>
<td>
- <%= link_to h(review.title), :controller => "reviews", :action => "view", :id => review.id -%>
+ <%= link_to_remote(h(review.title), :update => 'review', :url => {:action => 'show', :id => review.id}, :loading => 'onReviewLoading()', :complete => "onReviewLoaded()") -%>
</td>
<td><%= review.project.name -%>
<br/><span class="note"><%= review.resource.long_name -%></span></td>
@@ -138,6 +138,8 @@ function reviewIdFieldModified(field) {
</div>
+<div id="review-loading" style="display: none"><%= image_tag 'loading.gif' -%></div>
+<div id="review" style="display: none"></div>
<script>
$('review_id').focus();
</script> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/stylesheets/style.css b/sonar-server/src/main/webapp/stylesheets/style.css
index fd11c07a403..a6a1af75d40 100644
--- a/sonar-server/src/main/webapp/stylesheets/style.css
+++ b/sonar-server/src/main/webapp/stylesheets/style.css
@@ -686,7 +686,7 @@ ul.operations li img {
div.vtitle{
background-color:#E4ECF3;
margin:0;
- padding:0 0 0 10px;
+ padding:0 10px;
line-height: 2.2em;
text-shadow: 0 1px 0 #FFF;
color:#777
@@ -706,24 +706,9 @@ span.violation_date {
color:#777;
font-size:90%;
}
-div.review_permalink {
- float: right;
- background-color: #F4F4F4;
- border-color: #CDCDCD;
- border-style: none none none solid;
- border-width: 1px;
- color: #333333;
- font-size: 12px;
- font-weight: bold;
- margin: 0;
- padding: 0 10px;
- text-shadow: 1px 1px 0 #FFFFFF;
-}
span.review_permalink a {
color:#777;
font-size:90%;
- padding: 0 0 0 20px;
- background: url('../images/zoom.png') no-repeat left;
}
span.rulename a:hover {
text-decoration: underline;
diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/persistence/DatabaseTestCase.java b/sonar-testing-harness/src/main/java/org/sonar/test/persistence/DatabaseTestCase.java
index ba9e47b6e76..7c8d4543d6c 100644
--- a/sonar-testing-harness/src/main/java/org/sonar/test/persistence/DatabaseTestCase.java
+++ b/sonar-testing-harness/src/main/java/org/sonar/test/persistence/DatabaseTestCase.java
@@ -196,6 +196,20 @@ public abstract class DatabaseTestCase {
}
}
+ protected final void assertTables(String testName, String[] tables, String[] ignoreCols) {
+ try {
+ IDataSet dataSet = getCurrentDataSet();
+ IDataSet expectedDataSet = getExpectedData(testName);
+ for (String table : tables) {
+ Assertion.assertEqualsIgnoreCols(expectedDataSet.getTable(table), dataSet.getTable(table), ignoreCols);
+ }
+ } catch (DataSetException e) {
+ throw translateException("Error while checking results", e);
+ } catch (DatabaseUnitException e) {
+ fail(e.getMessage());
+ }
+ }
+
protected final void assertEmptyTables(String... emptyTables) {
for (String table : emptyTables) {
try {
diff --git a/sonar-testing-harness/src/main/resources/org/sonar/test/persistence/sonar-test.ddl b/sonar-testing-harness/src/main/resources/org/sonar/test/persistence/sonar-test.ddl
index c5125179cbf..54f49a0953b 100644
--- a/sonar-testing-harness/src/main/resources/org/sonar/test/persistence/sonar-test.ddl
+++ b/sonar-testing-harness/src/main/resources/org/sonar/test/persistence/sonar-test.ddl
@@ -492,6 +492,7 @@ CREATE TABLE REVIEWS (
STATUS VARCHAR(10),
SEVERITY VARCHAR(10),
RULE_FAILURE_PERMANENT_ID INTEGER,
+ PROJECT_ID INTEGER,
RESOURCE_ID INTEGER,
RESOURCE_LINE INTEGER,
primary key (id)