aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/project/ws/GhostsAction.java46
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/project/ws/GhostsActionTest.java182
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/all-projects.json14
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/pagination.json5
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java27
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java7
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml13
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java39
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java16
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/select_ghost_projects.xml335
10 files changed, 236 insertions, 448 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/GhostsAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/GhostsAction.java
index 5371b6be118..9f5b18afcc5 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/GhostsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/GhostsAction.java
@@ -32,30 +32,36 @@ import org.sonar.api.utils.text.JsonWriter;
import org.sonar.api.web.UserRole;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
-import org.sonar.db.MyBatis;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.es.SearchOptions;
+import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.user.UserSession;
import static com.google.common.collect.Sets.newHashSet;
import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
public class GhostsAction implements ProjectsWsAction {
- public static final String ACTION = "ghosts";
+ private static final String PARAM_ORGANIZATION = "organization";
+ private static final String ACTION = "ghosts";
private static final Set<String> POSSIBLE_FIELDS = newHashSet("uuid", "key", "name", "creationDate");
private final DbClient dbClient;
private final UserSession userSession;
+ private final DefaultOrganizationProvider defaultOrganizationProvider;
- public GhostsAction(DbClient dbClient, UserSession userSession) {
+ public GhostsAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider) {
this.dbClient = dbClient;
this.userSession = userSession;
+ this.defaultOrganizationProvider = defaultOrganizationProvider;
}
@Override
public void define(WebService.NewController context) {
- context
- .createAction(ACTION)
+ WebService.NewAction action = context.createAction(ACTION);
+
+ action
.setDescription("List ghost projects.<br /> Requires 'Administer System' permission.")
.setResponseExample(Resources.getResource(getClass(), "projects-example-ghosts.json"))
.setSince("5.2")
@@ -63,30 +69,46 @@ public class GhostsAction implements ProjectsWsAction {
.addFieldsParam(POSSIBLE_FIELDS)
.addSearchQuery("sonar", "names", "keys")
.setHandler(this);
+
+ action.createParam(PARAM_ORGANIZATION)
+ .setDescription("the organization key")
+ .setRequired(false)
+ .setInternal(true)
+ .setSince("6.3");
}
@Override
public void handle(Request request, Response response) throws Exception {
- userSession.checkPermission(UserRole.ADMIN);
- DbSession dbSession = dbClient.openSession(false);
+ userSession.checkLoggedIn();
+
SearchOptions searchOptions = new SearchOptions()
.setPage(request.mandatoryParamAsInt(Param.PAGE),
request.mandatoryParamAsInt(Param.PAGE_SIZE));
Set<String> desiredFields = fieldsToReturn(request.paramAsStrings(Param.FIELDS));
String query = request.param(Param.TEXT_QUERY);
- try {
- long nbOfProjects = dbClient.componentDao().countGhostProjects(dbSession, query);
- List<ComponentDto> projects = dbClient.componentDao().selectGhostProjects(dbSession, searchOptions.getOffset(), searchOptions.getLimit(), query);
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ OrganizationDto organization = getOrganization(dbSession, request);
+ userSession.checkOrganizationPermission(organization.getUuid(), UserRole.ADMIN);
+
+ long nbOfProjects = dbClient.componentDao().countGhostProjects(dbSession, organization.getUuid(), query);
+ List<ComponentDto> projects = dbClient.componentDao().selectGhostProjects(dbSession, organization.getUuid(), query,
+ searchOptions.getOffset(), searchOptions.getLimit());
JsonWriter json = response.newJsonWriter().beginObject();
writeProjects(json, projects, desiredFields);
searchOptions.writeJson(json, nbOfProjects);
json.endObject().close();
- } finally {
- MyBatis.closeQuietly(dbSession);
}
}
+ private OrganizationDto getOrganization(DbSession dbSession, Request request) {
+ String organizationKey = request.getParam(PARAM_ORGANIZATION)
+ .or(defaultOrganizationProvider.get()::getKey);
+ return checkFoundWithOptional(
+ dbClient.organizationDao().selectByKey(dbSession, organizationKey),
+ "No organization for key '%s'", organizationKey);
+ }
+
private static void writeProjects(JsonWriter json, List<ComponentDto> projects, Set<String> fieldsToReturn) {
json.name("projects");
json.beginArray();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/GhostsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/GhostsActionTest.java
index 7784dd3e565..6d352a59f03 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/GhostsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/GhostsActionTest.java
@@ -20,9 +20,12 @@
package org.sonar.server.project.ws;
import com.google.common.io.Resources;
+import java.util.function.Consumer;
import org.apache.commons.lang.StringUtils;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
@@ -35,98 +38,157 @@ import org.sonar.db.component.SnapshotDto;
import org.sonar.db.component.SnapshotTesting;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.ws.WsTester;
-import org.sonar.test.JsonAssert;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
+import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
+import static org.sonar.test.JsonAssert.assertJson;
public class GhostsActionTest {
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
-
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
- DbClient dbClient = db.getDbClient();
- WsTester ws = new WsTester(new ProjectsWs(new GhostsAction(dbClient, userSessionRule)));
+ private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
+ private DbClient dbClient = db.getDbClient();
+ private WsActionTester underTest = new WsActionTester(new GhostsAction(dbClient, userSessionRule, defaultOrganizationProvider));
+
+ @Test
+ public void verify_definition() {
+ WebService.Action action = underTest.getDef();
+ assertThat(action.description()).isEqualTo("List ghost projects.<br /> Requires 'Administer System' permission.");
+ assertThat(action.since()).isEqualTo("5.2");
+ assertThat(action.isInternal()).isFalse();
+
+ assertThat(action.params()).hasSize(5);
+
+ Param organization = action.param("organization");
+ assertThat(organization.description()).isEqualTo("the organization key");
+ assertThat(organization.since()).isEqualTo("6.3");
+ assertThat(organization.isRequired()).isFalse();
+ assertThat(organization.isInternal()).isTrue();
+ }
@Test
public void ghost_projects_without_analyzed_projects() throws Exception {
- userSessionRule.setGlobalPermissions(UserRole.ADMIN);
- insertNewGhostProject("1");
- insertNewGhostProject("2");
- insertNewActiveProject("3");
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto ghost1 = insertGhostProject(organization);
+ ComponentDto ghost2 = insertGhostProject(organization);
+ ComponentDto activeProject = insertActiveProject(organization);
+ userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
- WsTester.Result result = ws.newGetRequest("api/projects", "ghosts").execute();
+ TestResponse result = underTest.newRequest()
+ .setParam("organization", organization.getKey())
+ .execute();
- result.assertJson(getClass(), "all-projects.json");
- assertThat(result.outputAsString()).doesNotContain("analyzed-uuid-3");
+ String json = result.getInput();
+ assertJson(json).isSimilarTo("{" +
+ " \"projects\": [" +
+ " {" +
+ " \"uuid\": \"" + ghost1.uuid() + "\"," +
+ " \"key\": \"" + ghost1.key() + "\"," +
+ " \"name\": \"" + ghost1.name() + "\"" +
+ " }," +
+ " {" +
+ " \"uuid\": \"" + ghost2.uuid() + "\"," +
+ " \"key\": \"" + ghost2.key() + "\"," +
+ " \"name\": \"" + ghost2.name() + "\"" +
+ " }" +
+ " ]" +
+ "}");
+ assertThat(json).doesNotContain(activeProject.uuid());
}
@Test
public void ghost_projects_with_correct_pagination() throws Exception {
- userSessionRule.setGlobalPermissions(UserRole.ADMIN);
+ OrganizationDto organization = db.organizations().insert();
for (int i = 1; i <= 10; i++) {
- insertNewGhostProject(String.valueOf(i));
+ int count = i;
+ insertGhostProject(organization, dto -> dto.setKey("ghost-key-" + count));
}
+ userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
- WsTester.Result result = ws.newGetRequest("api/projects", "ghosts")
+ TestResponse result = underTest.newRequest()
+ .setParam("organization", organization.getKey())
.setParam(Param.PAGE, "3")
.setParam(Param.PAGE_SIZE, "4")
.execute();
- result.assertJson(getClass(), "pagination.json");
- assertThat(StringUtils.countMatches(result.outputAsString(), "ghost-uuid-")).isEqualTo(2);
+ String json = result.getInput();
+ assertJson(json).isSimilarTo("{" +
+ " \"p\": 3," +
+ " \"ps\": 4," +
+ " \"total\": 10" +
+ "}");
+ assertThat(StringUtils.countMatches(json, "ghost-key-")).isEqualTo(2);
}
@Test
public void ghost_projects_with_chosen_fields() throws Exception {
- userSessionRule.setGlobalPermissions(UserRole.ADMIN);
- insertNewGhostProject("1");
+ OrganizationDto organization = db.organizations().insert();
+ insertGhostProject(organization);
+ userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
- WsTester.Result result = ws.newGetRequest("api/projects", "ghosts")
+ TestResponse result = underTest.newRequest()
+ .setParam("organization", organization.getKey())
.setParam(Param.FIELDS, "name")
.execute();
- assertThat(result.outputAsString()).contains("uuid", "name")
+ assertThat(result.getInput())
+ .contains("uuid", "name")
.doesNotContain("key")
.doesNotContain("creationDate");
}
@Test
public void ghost_projects_with_partial_query_on_name() throws Exception {
- userSessionRule.setGlobalPermissions(UserRole.ADMIN);
+ OrganizationDto organization = db.organizations().insert();
+ insertGhostProject(organization, dto -> dto.setName("ghost-name-10"));
+ insertGhostProject(organization, dto -> dto.setName("ghost-name-11"));
+ insertGhostProject(organization, dto -> dto.setName("ghost-name-20"));
- insertNewGhostProject("10");
- insertNewGhostProject("11");
- insertNewGhostProject("2");
+ userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
- WsTester.Result result = ws.newGetRequest("api/projects", "ghosts")
+ TestResponse result = underTest.newRequest()
+ .setParam("organization", organization.getKey())
.setParam(Param.TEXT_QUERY, "name-1")
.execute();
- assertThat(result.outputAsString()).contains("ghost-name-10", "ghost-name-11")
+ assertThat(result.getInput())
+ .contains("ghost-name-10", "ghost-name-11")
.doesNotContain("ghost-name-2");
}
@Test
public void ghost_projects_with_partial_query_on_key() throws Exception {
- userSessionRule.setGlobalPermissions(UserRole.ADMIN);
+ OrganizationDto organization = db.organizations().insert();
+ insertGhostProject(organization, dto -> dto.setKey("ghost-key-1"));
- insertNewGhostProject("1");
+ userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
- WsTester.Result result = ws.newGetRequest("api/projects", "ghosts")
+ TestResponse result = underTest.newRequest()
+ .setParam("organization", organization.getKey())
.setParam(Param.TEXT_QUERY, "GHOST-key")
.execute();
- assertThat(result.outputAsString()).contains("ghost-key-1");
+ assertThat(result.getInput())
+ .contains("ghost-key-1");
}
@Test
public void ghost_projects_base_on_json_example() throws Exception {
- userSessionRule.setGlobalPermissions(UserRole.ADMIN);
OrganizationDto organizationDto = db.organizations().insert();
ComponentDto hBaseProject = ComponentTesting.newProjectDto(organizationDto, "ce4c03d6-430f-40a9-b777-ad877c00aa4d")
.setKey("org.apache.hbas:hbase")
@@ -134,38 +196,62 @@ public class GhostsActionTest {
.setCreatedAt(DateUtils.parseDateTime("2015-03-04T23:03:44+0100"));
dbClient.componentDao().insert(db.getSession(), hBaseProject);
dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(hBaseProject)
- .setStatus(SnapshotDto.STATUS_UNPROCESSED));
+ .setStatus(STATUS_UNPROCESSED));
ComponentDto roslynProject = ComponentTesting.newProjectDto(organizationDto, "c526ef20-131b-4486-9357-063fa64b5079")
.setKey("com.microsoft.roslyn:roslyn")
.setName("Roslyn")
.setCreatedAt(DateUtils.parseDateTime("2013-03-04T23:03:44+0100"));
dbClient.componentDao().insert(db.getSession(), roslynProject);
dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(roslynProject)
- .setStatus(SnapshotDto.STATUS_UNPROCESSED));
+ .setStatus(STATUS_UNPROCESSED));
db.getSession().commit();
+ userSessionRule.login().addOrganizationPermission(organizationDto, SYSTEM_ADMIN);
- WsTester.Result result = ws.newGetRequest("api/projects", "ghosts").execute();
+ TestResponse result = underTest.newRequest()
+ .setParam("organization", organizationDto.getKey())
+ .execute();
- JsonAssert.assertJson(result.outputAsString()).isSimilarTo(Resources.getResource(getClass(), "projects-example-ghosts.json"));
+ assertJson(result.getInput())
+ .isSimilarTo(Resources.getResource(getClass(), "projects-example-ghosts.json"));
}
@Test(expected = ForbiddenException.class)
public void fail_if_does_not_have_sufficient_rights() throws Exception {
- userSessionRule.setGlobalPermissions(UserRole.USER, UserRole.ISSUE_ADMIN, UserRole.CODEVIEWER);
+ userSessionRule.login()
+ .addOrganizationPermission(db.getDefaultOrganization(), UserRole.USER)
+ .addOrganizationPermission(db.getDefaultOrganization(), UserRole.ISSUE_ADMIN)
+ .addOrganizationPermission(db.getDefaultOrganization(), UserRole.CODEVIEWER);
- ws.newGetRequest("api/projects", "ghosts").execute();
+ underTest.newRequest().execute();
}
- private void insertNewGhostProject(String id) {
- ComponentDto project = ComponentTesting
- .newProjectDto(db.organizations().insert(), "ghost-uuid-" + id)
- .setName("ghost-name-" + id)
- .setKey("ghost-key-" + id);
- dbClient.componentDao().insert(db.getSession(), project);
- SnapshotDto snapshot = SnapshotTesting.newAnalysis(project)
- .setStatus(SnapshotDto.STATUS_UNPROCESSED);
- dbClient.snapshotDao().insert(db.getSession(), snapshot);
- db.getSession().commit();
+ @Test
+ public void fail_with_NotFoundException_when_organization_with_specified_key_does_not_exist() {
+ TestRequest request = underTest.newRequest()
+ .setParam("organization", "foo");
+ userSessionRule.login();
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("No organization for key 'foo'");
+
+ request.execute();
+ }
+
+ private ComponentDto insertGhostProject(OrganizationDto organization) {
+ return insertGhostProject(organization, dto -> {
+ });
+ }
+
+ private ComponentDto insertGhostProject(OrganizationDto organization, Consumer<ComponentDto> consumer) {
+ ComponentDto project = db.components().insertProject(organization, consumer);
+ db.components().insertSnapshot(project, dto -> dto.setStatus(STATUS_UNPROCESSED));
+ return project;
+ }
+
+ private ComponentDto insertActiveProject(OrganizationDto organization) {
+ ComponentDto project = db.components().insertProject(organization);
+ db.components().insertSnapshot(project, dto -> dto.setStatus(STATUS_PROCESSED));
+ return project;
}
private void insertNewActiveProject(String id) {
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/all-projects.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/all-projects.json
deleted file mode 100644
index cd1aad1fa60..00000000000
--- a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/all-projects.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "projects": [
- {
- "uuid": "ghost-uuid-1",
- "key": "ghost-key-1",
- "name": "ghost-name-1"
- },
- {
- "uuid": "ghost-uuid-2",
- "key": "ghost-key-2",
- "name": "ghost-name-2"
- }
- ]
-}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/pagination.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/pagination.json
deleted file mode 100644
index 8967d5cb758..00000000000
--- a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/pagination.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "p": 3,
- "ps": 4,
- "total": 10
-}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java
index d475e9ead20..7deb692a59a 100644
--- a/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java
+++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java
@@ -41,7 +41,6 @@ import org.sonar.db.DbSession;
import org.sonar.db.RowNotFoundException;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.Maps.newHashMapWithExpectedSize;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang.StringUtils.isBlank;
@@ -263,20 +262,12 @@ public class ComponentDao implements Dao {
return DatabaseUtils.buildLikeValue(textQuery.toUpperCase(Locale.ENGLISH), BEFORE_AND_AFTER);
}
- public List<ComponentDto> selectGhostProjects(DbSession session, int offset, int limit, @Nullable String query) {
- Map<String, Object> parameters = newHashMapWithExpectedSize(2);
- addProjectQualifier(parameters);
- addPartialQueryParameterIfNotNull(parameters, query);
-
- return mapper(session).selectGhostProjects(parameters, new RowBounds(offset, limit));
+ public List<ComponentDto> selectGhostProjects(DbSession session, String organizationUuid, @Nullable String query, int offset, int limit) {
+ return mapper(session).selectGhostProjects(organizationUuid, queryParameterFrom(query), new RowBounds(offset, limit));
}
- public long countGhostProjects(DbSession session, @Nullable String query) {
- Map<String, Object> parameters = newHashMapWithExpectedSize(2);
- addProjectQualifier(parameters);
- addPartialQueryParameterIfNotNull(parameters, query);
-
- return mapper(session).countGhostProjects(parameters);
+ public long countGhostProjects(DbSession session, String organizationUuid, @Nullable String query) {
+ return mapper(session).countGhostProjects(organizationUuid, queryParameterFrom(query));
}
/**
@@ -317,12 +308,16 @@ public class ComponentDao implements Dao {
private static void addPartialQueryParameterIfNotNull(Map<String, Object> parameters, @Nullable String keyOrNameFilter) {
// TODO rely on resource_index table and match exactly the key
if (keyOrNameFilter != null) {
- parameters.put("query", "%" + keyOrNameFilter.toUpperCase(Locale.ENGLISH) + "%");
+ parameters.put("query", queryParameterFrom(keyOrNameFilter));
}
}
- private static void addProjectQualifier(Map<String, Object> parameters) {
- parameters.put("qualifier", Qualifiers.PROJECT);
+ @CheckForNull
+ private static String queryParameterFrom(@Nullable String keyOrNameFilter) {
+ if (keyOrNameFilter != null) {
+ return "%" + keyOrNameFilter.toUpperCase(Locale.ENGLISH) + "%";
+ }
+ return null;
}
public void insert(DbSession session, ComponentDto item) {
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java
index 2cddadffbd4..23bda21a9e9 100644
--- a/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java
+++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java
@@ -21,7 +21,6 @@ package org.sonar.db.component;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
@@ -122,14 +121,14 @@ public interface ComponentMapper {
int countProvisioned(@Param("organizationUuid") String organizationUuid, @Nullable @Param("keyOrNameLike") String keyOrNameLike, @Param("qualifiers") Set<String> qualifiers);
- List<ComponentDto> selectGhostProjects(Map<String, Object> parameters, RowBounds rowBounds);
+ List<ComponentDto> selectGhostProjects(@Param("organizationUuid") String organizationUuid, @Nullable @Param("query") String query, RowBounds rowBounds);
+
+ long countGhostProjects(@Param("organizationUuid") String organizationUuid, @Nullable @Param("query") String query);
List<ComponentDto> selectComponentsHavingSameKeyOrderedById(String key);
List<ComponentDto> selectProjectsByNameQuery(@Param("nameQuery") @Nullable String nameQuery, @Param("includeModules") boolean includeModules);
- long countGhostProjects(Map<String, Object> parameters);
-
void selectForIndexing(@Param("projectUuid") @Nullable String projectUuid, ResultHandler handler);
void insert(ComponentDto componentDto);
diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml
index b8a97b3df49..79b19813fff 100644
--- a/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml
+++ b/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml
@@ -380,23 +380,24 @@
<select id="selectGhostProjects" parameterType="map" resultType="Component">
select distinct
- <include refid="componentColumns"/>
+ <include refid="componentColumns"/>
from projects p
- <include refid="ghostClauses"/>
+ <include refid="ghostProjectClauses"/>
</select>
<select id="countGhostProjects" parameterType="map" resultType="long">
- select count(p.id)
+ select
+ count(distinct p.id)
from projects p
- <include refid="ghostClauses"/>
+ <include refid="ghostProjectClauses"/>
</select>
- <sql id="ghostClauses">
+ <sql id="ghostProjectClauses">
inner join snapshots s1 on s1.component_uuid = p.uuid and s1.status='U'
left join snapshots s2 on s2.component_uuid = p.uuid and s2.status='P'
where
s2.id is null
- and p.qualifier=#{qualifier,jdbcType=VARCHAR}
+ and p.qualifier='TRK'
and p.copy_component_uuid is null
<if test="query!=null">
and (
diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java
index 646a54be31a..2ac7254b0ce 100644
--- a/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java
+++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java
@@ -49,6 +49,7 @@ import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.guava.api.Assertions.assertThat;
import static org.sonar.db.component.ComponentTesting.newDeveloper;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newProjectCopy;
@@ -596,13 +597,39 @@ public class ComponentDaoTest {
@Test
public void select_ghost_projects() {
- db.prepareDbUnit(getClass(), "select_ghost_projects.xml");
-
- List<ComponentDto> result = underTest.selectGhostProjects(dbSession, 0, 10, null);
+ OrganizationDto organization = db.organizations().insert();
- assertThat(result).hasSize(1);
- assertThat(result.get(0).key()).isEqualTo("org.ghost.project");
- assertThat(underTest.countGhostProjects(dbSession, null)).isEqualTo(1);
+ // ghosts because has at least one snapshot with status U but none with status P
+ ComponentDto ghostProject = db.components().insertProject(organization);
+ db.components().insertSnapshot(ghostProject, dto -> dto.setStatus("U"));
+ db.components().insertSnapshot(ghostProject, dto -> dto.setStatus("U"));
+ ComponentDto ghostProject2 = db.components().insertProject(organization);
+ db.components().insertSnapshot(ghostProject2, dto -> dto.setStatus("U"));
+ ComponentDto disabledGhostProject = db.components().insertProject(dto -> dto.setEnabled(false));
+ db.components().insertSnapshot(disabledGhostProject, dto -> dto.setStatus("U"));
+
+ ComponentDto project1 = db.components().insertProject(organization);
+ db.components().insertSnapshot(project1, dto -> dto.setStatus("P"));
+ db.components().insertSnapshot(project1, dto -> dto.setStatus("U"));
+ ComponentDto module = db.components().insertComponent(newModuleDto(project1));
+ ComponentDto dir = db.components().insertComponent(newDirectory(module, "foo"));
+ db.components().insertComponent(newFileDto(module, dir, "bar"));
+
+ ComponentDto provisionedProject = db.components().insertProject(organization);
+
+ // not a ghost because has at least one snapshot with status P
+ ComponentDto project2 = db.components().insertProject(organization);
+ db.components().insertSnapshot(project2, dto -> dto.setStatus("P"));
+
+ // not a ghost because it's not a project
+ ComponentDto view = db.components().insertView(organization);
+ db.components().insertSnapshot(view, dto -> dto.setStatus("U"));
+ db.components().insertComponent(newProjectCopy("do", project1, view));
+
+ assertThat(underTest.selectGhostProjects(dbSession, organization.getUuid(), null, 0, 10))
+ .extracting(ComponentDto::uuid)
+ .containsOnly(ghostProject.uuid(), ghostProject2.uuid(), disabledGhostProject.uuid());
+ assertThat(underTest.countGhostProjects(dbSession, organization.getUuid(), null)).isEqualTo(3);
}
@Test
diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java
index 67d438cb847..d8f93680b66 100644
--- a/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java
+++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java
@@ -95,7 +95,7 @@ public class ComponentDbTester {
}
public ComponentDto insertView(OrganizationDto organizationDto) {
- return insertComponentImpl(newView(organizationDto), dto -> {});
+ return insertComponentImpl(newView(organizationDto), noExtraConfiguration());
}
public ComponentDto insertView(OrganizationDto organizationDto, Consumer<ComponentDto> dtoPopulator) {
@@ -111,7 +111,7 @@ public class ComponentDbTester {
}
public ComponentDto insertDeveloper(String name, Consumer<ComponentDto> dtoPopulator) {
- return insertComponentImpl(newDeveloper(db.getDefaultOrganization(), name), noExtraConfiguration());
+ return insertComponentImpl(newDeveloper(db.getDefaultOrganization(), name), dtoPopulator);
}
public ComponentDto insertDeveloper(String name) {
@@ -145,4 +145,16 @@ public class ComponentDbTester {
db.commit();
return snapshot;
}
+
+ public SnapshotDto insertSnapshot(ComponentDto componentDto) {
+ return insertSnapshot(componentDto, noExtraConfiguration());
+ }
+
+ public SnapshotDto insertSnapshot(ComponentDto componentDto, Consumer<SnapshotDto> consumer) {
+ SnapshotDto snapshotDto = SnapshotTesting.newAnalysis(componentDto);
+ consumer.accept(snapshotDto);
+ SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, snapshotDto);
+ db.commit();
+ return snapshot;
+ }
}
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/select_ghost_projects.xml b/sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/select_ghost_projects.xml
deleted file mode 100644
index f5ad3efa9c2..00000000000
--- a/sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/select_ghost_projects.xml
+++ /dev/null
@@ -1,335 +0,0 @@
-<dataset>
-
- <!-- Struts projects is authorized for all user -->
- <group_roles id="1"
- group_id="[null]"
- resource_id="1"
- role="user"
- organization_uuid="org1"/>
-
- <!-- Ghost project -->
- <projects organization_uuid="org1"
- id="42"
- root_uuid="PPAA"
- scope="PRJ"
- qualifier="TRK"
- kee="org.ghost.project"
- name="Ghost Project"
- uuid="PPAA"
- uuid_path="NOT_USED"
- project_uuid="PPAA"
- module_uuid="[null]"
- module_uuid_path="."
- description="the description"
- long_name="Ghost Project"
- enabled="[true]"
- language="[null]"
- copy_component_uuid="[null]"
- developer_uuid="[null]"
- path="[null]"
- authorization_updated_at="123456789"/>
-
- <!-- root project -->
- <projects organization_uuid="org1"
- id="1"
- root_uuid="ABCD"
- scope="PRJ"
- qualifier="TRK"
- kee="org.struts:struts"
- deprecated_kee="org.struts:struts"
- name="Struts"
- uuid="ABCD"
- uuid_path="NOT_USED"
- project_uuid="ABCD"
- module_uuid="[null]"
- module_uuid_path=".ABCD."
- description="the description"
- long_name="Apache Struts"
- enabled="[true]"
- language="[null]"
- copy_component_uuid="[null]"
- developer_uuid="[null]"
- path="[null]"
- authorization_updated_at="123456789"/>
- <snapshots id="1"
- uuid="u1"
- component_uuid="ABCD"
- status="P"
- islast="[true]"
- purge_status="[null]"
- period1_mode="[null]"
- period1_param="[null]"
- period1_date="[null]"
- period2_mode="[null]"
- period2_param="[null]"
- period2_date="[null]"
- period3_mode="[null]"
- period3_param="[null]"
- period3_date="[null]"
- period4_mode="[null]"
- period4_param="[null]"
- period4_date="[null]"
- period5_mode="[null]"
- period5_param="[null]"
- period5_date="[null]"
- created_at="1228222680000"
- build_date="1228222680000"
- version="[null]"
- />
- <snapshots id="10"
- uuid="u10"
- component_uuid="ABCD"
- status="P"
- islast="[false]"
- purge_status="[null]"
- period1_mode="[null]"
- period1_param="[null]"
- period1_date="[null]"
- period2_mode="[null]"
- period2_param="[null]"
- period2_date="[null]"
- period3_mode="[null]"
- period3_param="[null]"
- period3_date="[null]"
- period4_mode="[null]"
- period4_param="[null]"
- period4_date="[null]"
- period5_mode="[null]"
- period5_param="[null]"
- period5_date="[null]"
- created_at="1228136280000"
- build_date="1228136280000"
- version="[null]"
- />
- <snapshots id="11"
- uuid="u11"
- component_uuid="PPAA"
- status="U"
- islast="[false]"
- purge_status="[null]"
- period1_mode="[null]"
- period1_param="[null]"
- period1_date="[null]"
- period2_mode="[null]"
- period2_param="[null]"
- period2_date="[null]"
- period3_mode="[null]"
- period3_param="[null]"
- period3_date="[null]"
- period4_mode="[null]"
- period4_param="[null]"
- period4_date="[null]"
- period5_mode="[null]"
- period5_param="[null]"
- period5_date="[null]"
- created_at="1228136280000"
- build_date="1228136280000"
- version="[null]"
- />
-
- <!-- module -->
- <projects organization_uuid="org1"
- id="2"
- root_uuid="ABCD"
- kee="org.struts:struts-core"
- name="Struts Core"
- uuid="EFGH"
- uuid_path="NOT_USED"
- project_uuid="ABCD"
- module_uuid="[null]"
- module_uuid_path=".ABCD.EFGH."
- scope="PRJ"
- qualifier="BRC"
- long_name="Struts Core"
- description="[null]"
- enabled="[true]"
- language="[null]"
- copy_component_uuid="[null]"
- developer_uuid="[null]"
- authorization_updated_at="[null]"/>
- <snapshots id="2"
- uuid="u2"
- component_uuid="EFGH"
- status="P"
- islast="[true]"
- purge_status="[null]"
- period1_mode="[null]"
- period1_param="[null]"
- period1_date="[null]"
- period2_mode="[null]"
- period2_param="[null]"
- period2_date="[null]"
- period3_mode="[null]"
- period3_param="[null]"
- period3_date="[null]"
- period4_mode="[null]"
- period4_param="[null]"
- period4_date="[null]"
- period5_mode="[null]"
- period5_param="[null]"
- period5_date="[null]"
- created_at="1228222680000"
- build_date="1228222680000"
- version="[null]"
- />
-
- <!-- directory -->
- <projects organization_uuid="org1"
- long_name="org.struts"
- id="3"
- scope="DIR"
- qualifier="DIR"
- kee="org.struts:struts-core:src/org/struts"
- uuid="GHIJ"
- uuid_path="NOT_USED"
- project_uuid="ABCD"
- module_uuid="EFGH"
- module_uuid_path=".ABCD.EFGH."
- name="src/org/struts"
- root_uuid="EFGH"
- description="[null]"
- enabled="[true]"
- language="[null]"
- copy_component_uuid="[null]"
- developer_uuid="[null]"
- path="src/org/struts"
- authorization_updated_at="[null]"/>
- <snapshots id="3"
- uuid="u3"
- component_uuid="GHIJ"
- status="P"
- islast="[true]"
- purge_status="[null]"
- period1_mode="[null]"
- period1_param="[null]"
- period1_date="[null]"
- period2_mode="[null]"
- period2_param="[null]"
- period2_date="[null]"
- period3_mode="[null]"
- period3_param="[null]"
- period3_date="[null]"
- period4_mode="[null]"
- period4_param="[null]"
- period4_date="[null]"
- period5_mode="[null]"
- period5_param="[null]"
- period5_date="[null]"
- created_at="1228222680000"
- build_date="1228222680000"
- version="[null]"
- />
-
- <!-- file -->
- <projects organization_uuid="org1"
- long_name="org.struts.RequestContext"
- id="4"
- scope="FIL"
- qualifier="FIL"
- kee="org.struts:struts-core:src/org/struts/RequestContext.java"
- uuid="KLMN"
- uuid_path="NOT_USED"
- project_uuid="ABCD"
- module_uuid="EFGH"
- module_uuid_path=".ABCD.EFGH."
- name="RequestContext.java"
- root_uuid="EFGH"
- description="[null]"
- enabled="[true]"
- language="java"
- copy_component_uuid="[null]"
- developer_uuid="[null]"
- path="src/org/struts/RequestContext.java"
- authorization_updated_at="[null]"/>
-
- <snapshots id="4"
- uuid="u4"
- component_uuid="KLMN"
- status="P"
- islast="[true]"
- purge_status="[null]"
- period1_mode="[null]"
- period1_param="[null]"
- period1_date="[null]"
- period2_mode="[null]"
- period2_param="[null]"
- period2_date="[null]"
- period3_mode="[null]"
- period3_param="[null]"
- period3_date="[null]"
- period4_mode="[null]"
- period4_param="[null]"
- period4_date="[null]"
- period5_mode="[null]"
- period5_param="[null]"
- period5_date="[null]"
- created_at="1228222680000"
- build_date="1228222680000"
- version="[null]"
- />
-
- <!-- Disabled projects -->
- <projects organization_uuid="org1"
- id="10"
- root_uuid="DCBA"
- scope="PRJ"
- qualifier="TRK"
- kee="org.disabled.project"
- name="Disabled Project"
- uuid="DCBA"
- uuid_path="NOT_USED"
- project_uuid="DCBA"
- module_uuid="[null]"
- module_uuid_path="."
- description="the description"
- long_name="Disabled project"
- enabled="[false]"
- language="[null]"
- copy_component_uuid="[null]"
- developer_uuid="[null]"
- path="[null]"
- authorization_updated_at="123456789"/>
-
- <!-- Developer and technical project copy -->
- <projects organization_uuid="org1"
- id="11"
- root_uuid="OPQR"
- scope="PRJ"
- qualifier="DEV"
- kee="DEV:anakin@skywalker.name"
- name="Anakin Skywalker"
- uuid="OPQR"
- uuid_path="NOT_USED"
- project_uuid="OPQR"
- module_uuid="[null]"
- module_uuid_path=".OPQR."
- description="the description"
- long_name="Anakin Skywalker"
- enabled="[true]"
- language="[null]"
- copy_component_uuid="[null]"
- developer_uuid="[null]"
- path="[null]"
- authorization_updated_at="123456789"/>
- <projects organization_uuid="org1"
- id="12"
- root_uuid="OPQR"
- scope="PRJ"
- qualifier="DEV_PRJ"
- kee="DEV:anakin@skywalker.name:org.struts:struts"
- name="Apache Struts"
- uuid="STUV"
- uuid_path="NOT_USED"
- project_uuid="OPQR"
- module_uuid="OPQR"
- module_uuid_path=".OPQR."
- description="the description"
- long_name="Apache Struts"
- enabled="[true]"
- language="[null]"
- copy_component_uuid="ABCD"
- developer_uuid="OPQR"
- path="[null]"
- authorization_updated_at="123456789"/>
-
-</dataset>