From e2a24d90b1cdf40d76228536cce3cd387dc9017e Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Mon, 11 May 2015 14:16:25 +0200 Subject: [PATCH] WS api/projects/ghosts list ghost projects - SONAR-6527 --- .../server/component/db/ComponentDao.java | 47 +++-- .../component/ws/ProjectsGhostsAction.java | 125 +++++++++++ ...on.java => ProjectsProvisionedAction.java} | 34 ++- .../sonar/server/component/ws/ProjectsWs.java | 4 +- .../server/platform/ServerComponents.java | 6 +- .../component/ws/projects-example-ghosts.json | 19 ++ .../server/component/db/ComponentDaoTest.java | 11 + .../ws/ProjectsGhostsActionTest.java | 199 ++++++++++++++++++ ...ava => ProjectsProvisionedActionTest.java} | 13 +- .../server/component/ws/ProjectsWsTest.java | 5 +- .../select_ghost_projects.xml | 109 ++++++++++ .../all-projects.json | 14 ++ .../ProjectsGhostsActionTest/pagination.json | 5 + .../all-projects.json | 0 .../core/component/db/ComponentMapper.java | 4 + .../core/component/db/ComponentMapper.xml | 31 ++- .../org/sonar/api/server/ws/WebService.java | 15 ++ 17 files changed, 598 insertions(+), 43 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsGhostsAction.java rename server/sonar-server/src/main/java/org/sonar/server/component/ws/{ProvisionedProjectsAction.java => ProjectsProvisionedAction.java} (84%) create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/component/ws/projects-example-ghosts.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsGhostsActionTest.java rename server/sonar-server/src/test/java/org/sonar/server/component/ws/{ProvisionedProjectsActionTest.java => ProjectsProvisionedActionTest.java} (92%) create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/db/ComponentDaoTest/select_ghost_projects.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsGhostsActionTest/all-projects.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsGhostsActionTest/pagination.json rename server/sonar-server/src/test/resources/org/sonar/server/component/ws/{ProvisionedProjectsActionTest => ProjectsProvisionedActionTest}/all-projects.json (100%) diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java index a57572426b6..eeb893b1cfb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java @@ -22,7 +22,6 @@ package org.sonar.server.component.db; import com.google.common.base.Function; import org.apache.ibatis.session.RowBounds; -import org.sonar.api.ServerComponent; import org.sonar.api.ServerSide; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Scopes; @@ -40,13 +39,13 @@ import org.sonar.server.exceptions.NotFoundException; import javax.annotation.CheckForNull; import javax.annotation.Nullable; - import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +import static com.google.common.collect.Maps.newHashMapWithExpectedSize; + /** * @since 4.3 */ @@ -176,20 +175,44 @@ public class ComponentDao extends BaseDao } public List selectProvisionedProjects(DbSession session, SearchOptions searchOptions, @Nullable String query) { - Map parameters = new HashMap<>(); - parameters.put("qualifier", Qualifiers.PROJECT); - if (query != null) { - parameters.put("query", "%" + query + "%"); - } + Map parameters = newHashMapWithExpectedSize(2); + addProjectQualifier(parameters); + addPartialQueryParameterIfNotNull(parameters, query); + return mapper(session).selectProvisionedProjects(parameters, new RowBounds(searchOptions.getOffset(), searchOptions.getLimit())); } public int countProvisionedProjects(DbSession session, @Nullable String query) { - Map parameters = new HashMap<>(); - parameters.put("qualifier", Qualifiers.PROJECT); + Map parameters = newHashMapWithExpectedSize(2); + addProjectQualifier(parameters); + addPartialQueryParameterIfNotNull(parameters, query); + + return mapper(session).countProvisionedProjects(parameters); + } + + public List selectGhostProjects(DbSession session, @Nullable String query, SearchOptions options) { + Map parameters = newHashMapWithExpectedSize(2); + addProjectQualifier(parameters); + addPartialQueryParameterIfNotNull(parameters, query); + + return mapper(session).selectGhostProjects(parameters, new RowBounds(options.getOffset(), options.getLimit())); + } + + public long countGhostProjects(DbSession session, @Nullable String query) { + Map parameters = newHashMapWithExpectedSize(2); + addProjectQualifier(parameters); + addPartialQueryParameterIfNotNull(parameters, query); + + return mapper(session).countGhostProjects(parameters); + } + + private void addPartialQueryParameterIfNotNull(Map parameters, @Nullable String query) { if (query != null) { - parameters.put("query", "%" + query + "%"); + parameters.put("query", "%" + query.toUpperCase() + "%"); } - return mapper(session).countProvisionedProjects(parameters); + } + + private void addProjectQualifier(Map parameters) { + parameters.put("qualifier", Qualifiers.PROJECT); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsGhostsAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsGhostsAction.java new file mode 100644 index 00000000000..86e488fc97f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsGhostsAction.java @@ -0,0 +1,125 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component.ws; + +import com.google.common.io.Resources; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.MyBatis; +import org.sonar.server.db.DbClient; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.user.UserSession; + +import javax.annotation.Nullable; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.Sets.newHashSet; + +public class ProjectsGhostsAction implements ProjectsWsAction { + public static final String ACTION = "ghosts"; + private static final Set POSSIBLE_FIELDS = newHashSet("uuid", "key", "name", "creationDate"); + + private final DbClient dbClient; + private final UserSession userSession; + + public ProjectsGhostsAction(DbClient dbClient, UserSession userSession) { + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + context + .createAction(ACTION) + .setDescription("List ghost projects.
Requires admin role.") + .setResponseExample(Resources.getResource(getClass(), "projects-example-ghosts.json")) + .setSince("5.2") + .addPagingParams(100) + .addFieldsParam(POSSIBLE_FIELDS) + .addSearchQuery("sonar", "names", "keys") + .setHandler(this); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession.checkGlobalPermission(UserRole.ADMIN); + DbSession dbSession = dbClient.openSession(false); + SearchOptions searchOptions = new SearchOptions() + .setPage(request.mandatoryParamAsInt(Param.PAGE), + request.mandatoryParamAsInt(Param.PAGE_SIZE)); + Set desiredFields = fieldsToReturn(request.paramAsStrings(Param.FIELDS)); + String query = request.param(Param.TEXT_QUERY); + + try { + long nbOfProjects = dbClient.componentDao().countGhostProjects(dbSession, query); + List projects = dbClient.componentDao().selectGhostProjects(dbSession, query, searchOptions); + JsonWriter json = response.newJsonWriter().beginObject(); + writeProjects(json, projects, desiredFields); + searchOptions.writeJson(json, nbOfProjects); + json.endObject().close(); + } finally { + MyBatis.closeQuietly(dbSession); + } + } + + private void writeProjects(JsonWriter json, List projects, Set fieldsToReturn) { + json.name("projects"); + json.beginArray(); + for (ComponentDto project : projects) { + json.beginObject(); + json.prop("uuid", project.uuid()); + writeIfWished(json, "key", project.key(), fieldsToReturn); + writeIfWished(json, "name", project.name(), fieldsToReturn); + writeIfWished(json, "creationDate", project.getCreatedAt(), fieldsToReturn); + json.endObject(); + } + json.endArray(); + } + + private void writeIfWished(JsonWriter json, String key, String value, Set fieldsToReturn) { + if (fieldsToReturn.contains(key)) { + json.prop(key, value); + } + } + + private void writeIfWished(JsonWriter json, String key, Date value, Set desiredFields) { + if (desiredFields.contains(key)) { + json.propDateTime(key, value); + } + } + + private Set fieldsToReturn(@Nullable List desiredFieldsFromRequest) { + if (desiredFieldsFromRequest == null) { + return POSSIBLE_FIELDS; + } + + return newHashSet(desiredFieldsFromRequest); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProvisionedProjectsAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsProvisionedAction.java similarity index 84% rename from server/sonar-server/src/main/java/org/sonar/server/component/ws/ProvisionedProjectsAction.java rename to server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsProvisionedAction.java index 9abbd5c5907..56954c40844 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProvisionedProjectsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsProvisionedAction.java @@ -34,24 +34,26 @@ import org.sonar.server.db.DbClient; import org.sonar.server.es.SearchOptions; import org.sonar.server.user.UserSession; -import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Set; -public class ProvisionedProjectsAction implements ProjectsWsAction { - private static final List POSSIBLE_FIELDS = Arrays.asList("uuid", "key", "name", "creationDate"); +import static com.google.common.collect.Sets.newHashSet; + +public class ProjectsProvisionedAction implements ProjectsWsAction { + private static final Set POSSIBLE_FIELDS = newHashSet("uuid", "key", "name", "creationDate"); private final DbClient dbClient; private final UserSession userSession; - public ProvisionedProjectsAction(DbClient dbClient, UserSession userSession) { + public ProjectsProvisionedAction(DbClient dbClient, UserSession userSession) { this.dbClient = dbClient; this.userSession = userSession; } @Override public void define(WebService.NewController controller) { - WebService.NewAction action = controller + controller .createAction("provisioned") .setDescription( "Get the list of provisioned projects.
" + @@ -60,12 +62,8 @@ public class ProvisionedProjectsAction implements ProjectsWsAction { .setResponseExample(Resources.getResource(getClass(), "projects-example-provisioned.json")) .setHandler(this) .addPagingParams(100) + .addSearchQuery("sonar", "names", "keys") .addFieldsParam(POSSIBLE_FIELDS); - - action - .createParam(Param.TEXT_QUERY) - .setDescription("UTF-8 search query") - .setExampleValue("sonar"); } @Override @@ -74,8 +72,8 @@ public class ProvisionedProjectsAction implements ProjectsWsAction { SearchOptions options = new SearchOptions().setPage( request.mandatoryParamAsInt(Param.PAGE), request.mandatoryParamAsInt(Param.PAGE_SIZE) - ); - List desiredFields = desiredFields(request); + ); + Set desiredFields = desiredFields(request); String query = request.param(Param.TEXT_QUERY); DbSession dbSession = dbClient.openSession(false); @@ -91,7 +89,7 @@ public class ProvisionedProjectsAction implements ProjectsWsAction { } } - private void writeProjects(List projects, JsonWriter json, List desiredFields) { + private void writeProjects(List projects, JsonWriter json, Set desiredFields) { json.name("projects"); json.beginArray(); for (ComponentDto project : projects) { @@ -105,24 +103,24 @@ public class ProvisionedProjectsAction implements ProjectsWsAction { json.endArray(); } - private void writeIfNeeded(JsonWriter json, String fieldName, String value, List desiredFields) { + private void writeIfNeeded(JsonWriter json, String fieldName, String value, Set desiredFields) { if (desiredFields.contains(fieldName)) { json.prop(fieldName, value); } } - private void writeIfNeeded(JsonWriter json, String fieldName, Date date, List desiredFields) { + private void writeIfNeeded(JsonWriter json, String fieldName, Date date, Set desiredFields) { if (desiredFields.contains(fieldName)) { json.propDateTime(fieldName, date); } } - private List desiredFields(Request request) { + private Set desiredFields(Request request) { List desiredFields = request.paramAsStrings(Param.FIELDS); if (desiredFields == null) { - desiredFields = POSSIBLE_FIELDS; + return POSSIBLE_FIELDS; } - return desiredFields; + return newHashSet(desiredFields); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsWs.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsWs.java index 13ab2556780..4b6e9e10501 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsWs.java @@ -25,6 +25,8 @@ import org.sonar.api.server.ws.RailsHandler; import org.sonar.api.server.ws.WebService; public class ProjectsWs implements WebService { + private static final String ENDPOINT = "api/projects"; + private final ProjectsWsAction[] actions; public ProjectsWs(ProjectsWsAction... actions) { @@ -33,7 +35,7 @@ public class ProjectsWs implements WebService { @Override public void define(Context context) { - NewController controller = context.createController("api/projects") + NewController controller = context.createController(ENDPOINT) .setSince("2.10") .setDescription("Projects management"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index cfaa9efc252..2dac82758dc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -109,8 +109,9 @@ import org.sonar.server.component.db.SnapshotDao; import org.sonar.server.component.ws.ComponentAppAction; import org.sonar.server.component.ws.ComponentsWs; import org.sonar.server.component.ws.EventsWs; +import org.sonar.server.component.ws.ProjectsGhostsAction; import org.sonar.server.component.ws.ProjectsWs; -import org.sonar.server.component.ws.ProvisionedProjectsAction; +import org.sonar.server.component.ws.ProjectsProvisionedAction; import org.sonar.server.component.ws.ResourcesWs; import org.sonar.server.computation.ComputationThreadLauncher; import org.sonar.server.computation.ReportQueue; @@ -801,7 +802,8 @@ class ServerComponents { pico.addSingleton(org.sonar.server.component.ws.SearchAction.class); pico.addSingleton(EventsWs.class); pico.addSingleton(ComponentCleanerService.class); - pico.addSingleton(ProvisionedProjectsAction.class); + pico.addSingleton(ProjectsProvisionedAction.class); + pico.addSingleton(ProjectsGhostsAction.class); // views pico.addSingleton(ViewIndexDefinition.class); diff --git a/server/sonar-server/src/main/resources/org/sonar/server/component/ws/projects-example-ghosts.json b/server/sonar-server/src/main/resources/org/sonar/server/component/ws/projects-example-ghosts.json new file mode 100644 index 00000000000..4bb9f843654 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/component/ws/projects-example-ghosts.json @@ -0,0 +1,19 @@ +{ + "projects": [ + { + "uuid": "ce4c03d6-430f-40a9-b777-ad877c00aa4d", + "key": "org.apache.hbas:hbase", + "name": "HBase", + "creationDate": "2015-03-04T23:03:44+0100" + }, + { + "uuid": "c526ef20-131b-4486-9357-063fa64b5079", + "key": "com.microsoft.roslyn:roslyn", + "name": "Roslyn", + "creationDate": "2013-03-04T23:03:44+0100" + } + ], + "total": 2, + "p": 1, + "ps": 100 +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java index ff3d0b1fecb..4c784d43ed5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java @@ -575,4 +575,15 @@ public class ComponentDaoTest { assertThat(numberOfProjects).isEqualTo(1); } + + @Test + public void select_ghost_projects() throws Exception { + db.prepareDbUnit(getClass(), "select_ghost_projects.xml"); + + List result = sut.selectGhostProjects(session, null, new SearchOptions()); + + assertThat(result).hasSize(1); + assertThat(result.get(0).key()).isEqualTo("org.ghost.project"); + assertThat(sut.countGhostProjects(session, null)).isEqualTo(1); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsGhostsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsGhostsActionTest.java new file mode 100644 index 00000000000..469f9dce4ec --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsGhostsActionTest.java @@ -0,0 +1,199 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component.ws; + +import com.google.common.io.Resources; +import org.apache.commons.lang.StringUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.component.SnapshotDto; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.server.component.ComponentTesting; +import org.sonar.server.component.SnapshotTesting; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.component.db.SnapshotDao; +import org.sonar.server.db.DbClient; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsTester; +import org.sonar.test.JsonAssert; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectsGhostsActionTest { + + @ClassRule + public static DbTester db = new DbTester(); + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + WsTester ws; + + DbClient dbClient; + DbSession dbSession; + + @Before + public void setUp() { + dbClient = new DbClient(db.database(), db.myBatis(), new ComponentDao(), new SnapshotDao(System2.INSTANCE)); + dbSession = dbClient.openSession(false); + ws = new WsTester(new ProjectsWs(new ProjectsGhostsAction(dbClient, userSessionRule))); + db.truncateTables(); + } + + @After + public void tearDown() throws Exception { + dbSession.close(); + } + + @Test + public void ghost_projects_without_analyzed_projects() throws Exception { + userSessionRule.setGlobalPermissions(UserRole.ADMIN); + insertNewGhostProject("1"); + insertNewGhostProject("2"); + insertNewActiveProject("3"); + + WsTester.Result result = ws.newGetRequest("api/projects", "ghosts").execute(); + + result.assertJson(getClass(), "all-projects.json"); + assertThat(result.outputAsString()).doesNotContain("analyzed-uuid-3"); + } + + @Test + public void ghost_projects_with_correct_pagination() throws Exception { + userSessionRule.setGlobalPermissions(UserRole.ADMIN); + for (int i = 1; i <= 10; i++) { + insertNewGhostProject(String.valueOf(i)); + } + + WsTester.Result result = ws.newGetRequest("api/projects", "ghosts") + .setParam(Param.PAGE, "3") + .setParam(Param.PAGE_SIZE, "4") + .execute(); + + result.assertJson(getClass(), "pagination.json"); + assertThat(StringUtils.countMatches(result.outputAsString(), "ghost-uuid-")).isEqualTo(2); + } + + @Test + public void ghost_projects_with_chosen_fields() throws Exception { + userSessionRule.setGlobalPermissions(UserRole.ADMIN); + insertNewGhostProject("1"); + + WsTester.Result result = ws.newGetRequest("api/projects", "ghosts") + .setParam(Param.FIELDS, "name") + .execute(); + + assertThat(result.outputAsString()).contains("uuid", "name") + .doesNotContain("key") + .doesNotContain("creationDate"); + } + + @Test + public void ghost_projects_with_partial_query_on_name() throws Exception { + userSessionRule.setGlobalPermissions(UserRole.ADMIN); + + insertNewGhostProject("10"); + insertNewGhostProject("11"); + insertNewGhostProject("2"); + + WsTester.Result result = ws.newGetRequest("api/projects", "ghosts") + .setParam(Param.TEXT_QUERY, "name-1") + .execute(); + + assertThat(result.outputAsString()).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); + + insertNewGhostProject("1"); + + WsTester.Result result = ws.newGetRequest("api/projects", "ghosts") + .setParam(Param.TEXT_QUERY, "GHOST-key") + .execute(); + + assertThat(result.outputAsString()).contains("ghost-key-1"); + } + + @Test + public void ghost_projects_base_on_json_example() throws Exception { + userSessionRule.setGlobalPermissions(UserRole.ADMIN); + ComponentDto hBaseProject = ComponentTesting.newProjectDto("ce4c03d6-430f-40a9-b777-ad877c00aa4d") + .setKey("org.apache.hbas:hbase") + .setName("HBase") + .setCreatedAt(DateUtils.parseDateTime("2015-03-04T23:03:44+0100")); + hBaseProject = dbClient.componentDao().insert(dbSession, hBaseProject); + dbClient.snapshotDao().insert(dbSession, SnapshotTesting.createForProject(hBaseProject) + .setStatus(SnapshotDto.STATUS_UNPROCESSED)); + ComponentDto roslynProject = ComponentTesting.newProjectDto("c526ef20-131b-4486-9357-063fa64b5079") + .setKey("com.microsoft.roslyn:roslyn") + .setName("Roslyn") + .setCreatedAt(DateUtils.parseDateTime("2013-03-04T23:03:44+0100")); + roslynProject = dbClient.componentDao().insert(dbSession, roslynProject); + dbClient.snapshotDao().insert(dbSession, SnapshotTesting.createForProject(roslynProject) + .setStatus(SnapshotDto.STATUS_UNPROCESSED)); + dbSession.commit(); + + WsTester.Result result = ws.newGetRequest("api/projects", "ghosts").execute(); + + JsonAssert.assertJson(result.outputAsString()).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); + + ws.newGetRequest("api/projects", "ghosts").execute(); + } + + private void insertNewGhostProject(String id) { + ComponentDto project = ComponentTesting + .newProjectDto("ghost-uuid-" + id) + .setName("ghost-name-" + id) + .setKey("ghost-key-" + id); + project = dbClient.componentDao().insert(dbSession, project); + SnapshotDto snapshot = SnapshotTesting.createForProject(project) + .setStatus(SnapshotDto.STATUS_UNPROCESSED); + dbClient.snapshotDao().insert(dbSession, snapshot); + dbSession.commit(); + } + + private void insertNewActiveProject(String id) { + ComponentDto project = ComponentTesting + .newProjectDto("analyzed-uuid-" + id) + .setName("analyzed-name-" + id) + .setKey("analyzed-key-" + id); + project = dbClient.componentDao().insert(dbSession, project); + SnapshotDto snapshot = SnapshotTesting.createForProject(project); + dbClient.snapshotDao().insert(dbSession, snapshot); + dbSession.commit(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProvisionedProjectsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsProvisionedActionTest.java similarity index 92% rename from server/sonar-server/src/test/java/org/sonar/server/component/ws/ProvisionedProjectsActionTest.java rename to server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsProvisionedActionTest.java index 669c3054546..927c4e4128f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProvisionedProjectsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsProvisionedActionTest.java @@ -47,7 +47,7 @@ import org.sonar.test.JsonAssert; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -public class ProvisionedProjectsActionTest { +public class ProjectsProvisionedActionTest { @ClassRule public static DbTester db = new DbTester(); @@ -71,7 +71,7 @@ public class ProvisionedProjectsActionTest { dbSession = dbClient.openSession(false); componentDao = dbClient.componentDao(); db.truncateTables(); - ws = new WsTester(new ProjectsWs(new ProvisionedProjectsAction(dbClient, userSessionRule))); + ws = new WsTester(new ProjectsWs(new ProjectsProvisionedAction(dbClient, userSessionRule))); } @Test @@ -79,18 +79,19 @@ public class ProvisionedProjectsActionTest { userSessionRule.setGlobalPermissions(UserRole.ADMIN); ComponentDto analyzedProject = ComponentTesting.newProjectDto("analyzed-uuid-1"); componentDao.insert(dbSession, newProvisionedProject("1"), newProvisionedProject("2")); - analyzedProject = componentDao.insert(dbSession, analyzedProject); + analyzedProject = dbClient.componentDao().insert(dbSession, analyzedProject); SnapshotDto snapshot = SnapshotTesting.createForProject(analyzedProject); dbClient.snapshotDao().insert(dbSession, snapshot); dbSession.commit(); - WsTester.TestRequest request = ws.newGetRequest("api/projects", "provisioned"); + WsTester.Result result = ws.newGetRequest("api/projects", "provisioned").execute(); - request.execute().assertJson(getClass(), "all-projects.json"); + result.assertJson(getClass(), "all-projects.json"); + assertThat(result.outputAsString()).doesNotContain("analyzed-uuid-1"); } @Test - public void provisioned_projects_with_correct_paginated() throws Exception { + public void provisioned_projects_with_correct_pagination() throws Exception { userSessionRule.setGlobalPermissions(UserRole.ADMIN); for (int i = 1; i <= 10; i++) { componentDao.insert(dbSession, newProvisionedProject(String.valueOf(i))); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsWsTest.java index 94f05808947..003bb4fa72a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsWsTest.java @@ -31,11 +31,12 @@ import static org.assertj.core.api.Assertions.assertThat; public class ProjectsWsTest { WebService.Controller controller; + WsTester ws; @Before public void setUp() { - WsTester tester = new WsTester(new ProjectsWs()); - controller = tester.controller("api/projects"); + ws = new WsTester(new ProjectsWs()); + controller = ws.controller("api/projects"); } @Test diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/db/ComponentDaoTest/select_ghost_projects.xml b/server/sonar-server/src/test/resources/org/sonar/server/component/db/ComponentDaoTest/select_ghost_projects.xml new file mode 100644 index 00000000000..e7af741beb5 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/db/ComponentDaoTest/select_ghost_projects.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsGhostsActionTest/all-projects.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsGhostsActionTest/all-projects.json new file mode 100644 index 00000000000..cd1aad1fa60 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsGhostsActionTest/all-projects.json @@ -0,0 +1,14 @@ +{ + "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/component/ws/ProjectsGhostsActionTest/pagination.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsGhostsActionTest/pagination.json new file mode 100644 index 00000000000..8967d5cb758 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsGhostsActionTest/pagination.json @@ -0,0 +1,5 @@ +{ + "p": 3, + "ps": 4, + "total": 10 +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProvisionedProjectsActionTest/all-projects.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsProvisionedActionTest/all-projects.json similarity index 100% rename from server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProvisionedProjectsActionTest/all-projects.json rename to server/sonar-server/src/test/resources/org/sonar/server/component/ws/ProjectsProvisionedActionTest/all-projects.json diff --git a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java index 6ed690b97d6..aa3960c8747 100644 --- a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java @@ -107,4 +107,8 @@ public interface ComponentMapper { List selectProvisionedProjects(Map parameters, RowBounds rowBounds); int countProvisionedProjects(Map parameters); + + List selectGhostProjects(Map parameters, RowBounds rowBounds); + + long countGhostProjects(Map parameters); } diff --git a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml index 84c011c08f2..92d5d1180dc 100644 --- a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml @@ -248,9 +248,36 @@ and p.copy_resource_id is null and ( - p.name like #{query} - or p.kee like #{query} + UPPER(p.name) like #{query} + or UPPER(p.kee) like #{query} ) + + + + + + + inner join snapshots s1 on s1.project_id = p.id and s1.status='U' + left join snapshots s2 on s2.project_id = p.id and s2.status='P' + where + s2.id is null + and p.qualifier=#{qualifier} + and p.copy_resource_id is null + + and ( + UPPER(p.name) like #{query} + or UPPER(p.kee) like #{query} + ) + + diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java index 43e0d461c0a..22b83f19711 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java @@ -19,6 +19,7 @@ */ package org.sonar.api.server.ws; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; @@ -356,6 +357,20 @@ public interface WebService extends Definable { return this; } + /**$ + * + * Creates the parameter {@link org.sonar.api.server.ws.WebService.Param#TEXT_QUERY}, which is + * used to search for a subset of fields containing the supplied string.
+ * The fields must be in the plural form (ex: "names", "keys") + */ + public NewAction addSearchQuery(String exampleValue, String... pluralFields) { + String actionDescription = String.format("Searches for %s containing the supplied string.", Joiner.on(" and ").join(pluralFields)); + createParam(Param.TEXT_QUERY) + .setDescription(actionDescription) + .setExampleValue(exampleValue); + return this; + } + /** * Add predefined parameters related to sorting of results. */ -- 2.39.5