@@ -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<ComponentMapper, ComponentDto, String> | |||
} | |||
public List<ComponentDto> selectProvisionedProjects(DbSession session, SearchOptions searchOptions, @Nullable String query) { | |||
Map<String, String> parameters = new HashMap<>(); | |||
parameters.put("qualifier", Qualifiers.PROJECT); | |||
if (query != null) { | |||
parameters.put("query", "%" + query + "%"); | |||
} | |||
Map<String, String> 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<String, String> parameters = new HashMap<>(); | |||
parameters.put("qualifier", Qualifiers.PROJECT); | |||
Map<String, String> parameters = newHashMapWithExpectedSize(2); | |||
addProjectQualifier(parameters); | |||
addPartialQueryParameterIfNotNull(parameters, query); | |||
return mapper(session).countProvisionedProjects(parameters); | |||
} | |||
public List<ComponentDto> selectGhostProjects(DbSession session, @Nullable String query, SearchOptions options) { | |||
Map<String, String> 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<String, String> parameters = newHashMapWithExpectedSize(2); | |||
addProjectQualifier(parameters); | |||
addPartialQueryParameterIfNotNull(parameters, query); | |||
return mapper(session).countGhostProjects(parameters); | |||
} | |||
private void addPartialQueryParameterIfNotNull(Map<String, String> 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<String, String> parameters) { | |||
parameters.put("qualifier", Qualifiers.PROJECT); | |||
} | |||
} |
@@ -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<String> 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.<br /> 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<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, 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<ComponentDto> projects, Set<String> 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<String> fieldsToReturn) { | |||
if (fieldsToReturn.contains(key)) { | |||
json.prop(key, value); | |||
} | |||
} | |||
private void writeIfWished(JsonWriter json, String key, Date value, Set<String> desiredFields) { | |||
if (desiredFields.contains(key)) { | |||
json.propDateTime(key, value); | |||
} | |||
} | |||
private Set<String> fieldsToReturn(@Nullable List<String> desiredFieldsFromRequest) { | |||
if (desiredFieldsFromRequest == null) { | |||
return POSSIBLE_FIELDS; | |||
} | |||
return newHashSet(desiredFieldsFromRequest); | |||
} | |||
} |
@@ -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<String> 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<String> 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.<br /> " + | |||
@@ -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<String> desiredFields = desiredFields(request); | |||
); | |||
Set<String> 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<ComponentDto> projects, JsonWriter json, List<String> desiredFields) { | |||
private void writeProjects(List<ComponentDto> projects, JsonWriter json, Set<String> 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<String> desiredFields) { | |||
private void writeIfNeeded(JsonWriter json, String fieldName, String value, Set<String> desiredFields) { | |||
if (desiredFields.contains(fieldName)) { | |||
json.prop(fieldName, value); | |||
} | |||
} | |||
private void writeIfNeeded(JsonWriter json, String fieldName, Date date, List<String> desiredFields) { | |||
private void writeIfNeeded(JsonWriter json, String fieldName, Date date, Set<String> desiredFields) { | |||
if (desiredFields.contains(fieldName)) { | |||
json.propDateTime(fieldName, date); | |||
} | |||
} | |||
private List<String> desiredFields(Request request) { | |||
private Set<String> desiredFields(Request request) { | |||
List<String> desiredFields = request.paramAsStrings(Param.FIELDS); | |||
if (desiredFields == null) { | |||
desiredFields = POSSIBLE_FIELDS; | |||
return POSSIBLE_FIELDS; | |||
} | |||
return desiredFields; | |||
return newHashSet(desiredFields); | |||
} | |||
} |
@@ -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"); | |||
@@ -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); |
@@ -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 | |||
} |
@@ -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<ComponentDto> 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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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))); |
@@ -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 |
@@ -0,0 +1,109 @@ | |||
<dataset> | |||
<!-- Struts projects is authorized for all user --> | |||
<group_roles id="1" group_id="[null]" resource_id="1" role="user"/> | |||
<!-- Ghost project --> | |||
<projects id="42" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.ghost.project" name="Ghost Project" | |||
uuid="PPAA" project_uuid="PPAA" module_uuid="[null]" module_uuid_path="." | |||
description="the description" long_name="Ghost Project" | |||
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" authorization_updated_at="123456789" /> | |||
<!-- root project --> | |||
<projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" deprecated_kee="org.struts:struts" name="Struts" | |||
uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD." | |||
description="the description" long_name="Apache Struts" | |||
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" authorization_updated_at="123456789" /> | |||
<snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]" | |||
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]" | |||
depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000" | |||
version="[null]" path=""/> | |||
<snapshots id="10" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]" | |||
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]" | |||
depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228136280000" build_date="1228136280000" | |||
version="[null]" path=""/> | |||
<snapshots id="11" project_id="42" parent_snapshot_id="[null]" root_project_id="42" root_snapshot_id="[null]" | |||
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]" | |||
depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228136280000" build_date="1228136280000" | |||
version="[null]" path=""/> | |||
<!-- module --> | |||
<projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core" | |||
uuid="EFGH" 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_resource_id="[null]" person_id="[null]" authorization_updated_at="[null]" /> | |||
<snapshots id="2" project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1" | |||
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]" | |||
depth="[null]" scope="PRJ" qualifier="BRC" created_at="1228222680000" build_date="1228222680000" | |||
version="[null]" path="1."/> | |||
<!-- directory --> | |||
<projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:src/org/struts" | |||
uuid="GHIJ" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH." | |||
name="src/org/struts" root_id="2" | |||
description="[null]" | |||
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="src/org/struts" authorization_updated_at="[null]" /> | |||
<snapshots id="3" project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1" | |||
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]" | |||
depth="[null]" scope="DIR" qualifier="PAC" created_at="1228222680000" build_date="1228222680000" | |||
version="[null]" path="1.2."/> | |||
<!-- file --> | |||
<projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="FIL" kee="org.struts:struts-core:src/org/struts/RequestContext.java" | |||
uuid="KLMN" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH." | |||
name="RequestContext.java" root_id="2" | |||
description="[null]" | |||
enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="src/org/struts/RequestContext.java" authorization_updated_at="[null]" /> | |||
<snapshots id="4" project_id="4" parent_snapshot_id="3" root_project_id="1" root_snapshot_id="1" | |||
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]" | |||
depth="[null]" scope="FIL" qualifier="CLA" created_at="1228222680000" build_date="1228222680000" | |||
version="[null]" path="1.2.3."/> | |||
<!-- Disabled projects --> | |||
<projects id="10" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.disabled.project" name="Disabled Project" | |||
uuid="DCBA" project_uuid="DCBA" module_uuid="[null]" module_uuid_path="." | |||
description="the description" long_name="Disabled project" | |||
enabled="[false]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" authorization_updated_at="123456789" /> | |||
<!-- Developer and technical project copy --> | |||
<projects id="11" root_id="[null]" scope="PRJ" qualifier="DEV" kee="DEV:anakin@skywalker.name" name="Anakin Skywalker" | |||
uuid="OPQR" project_uuid="OPQR" module_uuid="[null]" module_uuid_path=".OPQR." | |||
description="the description" long_name="Anakin Skywalker" | |||
enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" authorization_updated_at="123456789" /> | |||
<projects id="12" root_id="11" scope="PRJ" qualifier="DEV_PRJ" kee="DEV:anakin@skywalker.name:org.struts:struts" name="Apache Struts" | |||
uuid="STUV" project_uuid="OPQR" module_uuid="OPQR" module_uuid_path=".OPQR." | |||
description="the description" long_name="Apache Struts" | |||
enabled="[true]" language="[null]" copy_resource_id="1" person_id="11" path="[null]" authorization_updated_at="123456789" /> | |||
</dataset> |
@@ -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" | |||
} | |||
] | |||
} |
@@ -0,0 +1,5 @@ | |||
{ | |||
"p": 3, | |||
"ps": 4, | |||
"total": 10 | |||
} |
@@ -107,4 +107,8 @@ public interface ComponentMapper { | |||
List<ComponentDto> selectProvisionedProjects(Map<String, String> parameters, RowBounds rowBounds); | |||
int countProvisionedProjects(Map<String, String> parameters); | |||
List<ComponentDto> selectGhostProjects(Map<String, String> parameters, RowBounds rowBounds); | |||
long countGhostProjects(Map<String, String> parameters); | |||
} |
@@ -248,9 +248,36 @@ | |||
and p.copy_resource_id is null | |||
<if test="query!=null"> | |||
and ( | |||
p.name like #{query} | |||
or p.kee like #{query} | |||
UPPER(p.name) like #{query} | |||
or UPPER(p.kee) like #{query} | |||
) | |||
</if> | |||
</sql> | |||
<select id="selectGhostProjects" parameterType="map" resultType="Component"> | |||
select distinct <include refid="componentColumns"/> | |||
from projects p | |||
<include refid="ghostClauses"/> | |||
</select> | |||
<select id="countGhostProjects" parameterType="map" resultType="long"> | |||
select count(p.id) | |||
from projects p | |||
<include refid="ghostClauses"/> | |||
</select> | |||
<sql id="ghostClauses"> | |||
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 | |||
<if test="query!=null"> | |||
and ( | |||
UPPER(p.name) like #{query} | |||
or UPPER(p.kee) like #{query} | |||
) | |||
</if> | |||
</sql> | |||
</mapper> |
@@ -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<WebService.Context> { | |||
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.<br /> | |||
* The fields must be in the <strong>plural</strong> 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. | |||
*/ |