From e38085cd200784a4ce3898a39c2f085773d9c516 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Fri, 26 Sep 2014 10:40:08 +0200 Subject: [PATCH] SONAR-5531 Return sub projects in components list of issues search ws --- .../server/component/db/ComponentDao.java | 26 +++++++ .../java/org/sonar/server/db/BaseDao.java | 39 +++++----- .../java/org/sonar/server/db/DbClient.java | 1 + .../org/sonar/server/issue/db/IssueDao.java | 8 +- .../sonar/server/issue/ws/SearchAction.java | 4 + .../server/component/db/ComponentDaoTest.java | 78 +++++++++++++++++++ .../sonar/server/issue/db/IssueDaoTest.java | 38 +++++++++ .../issue/ws/SearchActionMediumTest.java | 36 ++++++++- .../components_contains_sub_projects.json | 18 +++++ .../sonar/core/component/ComponentDto.java | 4 +- .../core/component/db/ComponentMapper.java | 10 +++ .../org/sonar/core/issue/db/IssueMapper.java | 2 + .../core/component/db/ComponentMapper.xml | 41 ++++++++++ .../org/sonar/core/issue/db/IssueMapper.xml | 13 ++++ 14 files changed, 295 insertions(+), 23 deletions(-) create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/components_contains_sub_projects.json 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 fc14d1aeeb7..9834814d004 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 @@ -19,6 +19,7 @@ */ package org.sonar.server.component.db; +import com.google.common.collect.Lists; import org.sonar.api.ServerComponent; import org.sonar.api.utils.System2; import org.sonar.core.component.AuthorizedComponentDto; @@ -31,8 +32,12 @@ import org.sonar.server.exceptions.NotFoundException; import javax.annotation.CheckForNull; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import static com.google.common.collect.Lists.newArrayList; + /** * @since 4.3 */ @@ -85,6 +90,27 @@ public class ComponentDao extends BaseDao return mapper(session).findModulesByProject(projectKey); } + public List findSubProjectsByComponentKeys(DbSession session, Collection keys) { + return mapper(session).findSubProjectsByComponentKeys(keys); + } + + public List getByIds(DbSession session, Collection ids) { + if (ids.isEmpty()) { + return Collections.emptyList(); + } + List components = newArrayList(); + List> partitionList = Lists.partition(newArrayList(ids), 1000); + for (List partition : partitionList) { + List dtos = mapper(session).findByIds(partition); + components.addAll(dtos); + } + return components; + } + + protected List doGetByKeys(DbSession session, Collection keys) { + return mapper(session).findByKeys(keys); + } + @CheckForNull public AuthorizedComponentDto getNullableAuthorizedComponentById(Long id, DbSession session) { return mapper(session).selectAuthorizedComponentById(id); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java b/server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java index 165246d5036..388cde811a4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java @@ -22,6 +22,7 @@ package org.sonar.server.db; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import org.apache.ibatis.session.ResultContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,24 +33,16 @@ import org.sonar.core.persistence.Dto; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.search.DbSynchronizationHandler; import org.sonar.server.search.IndexDefinition; -import org.sonar.server.search.action.DeleteKey; -import org.sonar.server.search.action.DeleteNestedItem; -import org.sonar.server.search.action.InsertDto; -import org.sonar.server.search.action.RefreshIndex; -import org.sonar.server.search.action.UpsertDto; -import org.sonar.server.search.action.UpsertNestedItem; +import org.sonar.server.search.action.*; import javax.annotation.CheckForNull; import javax.annotation.Nullable; + import java.io.Serializable; import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; +import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; /** @@ -168,14 +161,20 @@ public abstract class BaseDao, KEY extends Serializ } public List getByKeys(DbSession session, Collection keys) { - List results = new ArrayList(); - for (KEY key : keys) { - DTO result = this.getNullableByKey(session, key); - if (result != null) { - results.add(result); - } + if (keys.isEmpty()) { + return Collections.emptyList(); + } + List components = newArrayList(); + List> partitionList = Lists.partition(newArrayList(keys), 1000); + for (List partition : partitionList) { + List dtos = doGetByKeys(session, partition); + components.addAll(dtos); } - return results; + return components; + } + + protected List doGetByKeys(DbSession session, Collection keys) { + throw notImplemented(this); } @Override @@ -348,7 +347,7 @@ public abstract class BaseDao, KEY extends Serializ return getSynchronizationParams(date, Collections.emptyMap()); } - protected Map getSynchronizationParams(Date date, Map params) { + protected Map getSynchronizationParams(Date date, Map params) { Map finalParams = newHashMap(); finalParams.put("date", new Timestamp(date.getTime())); return finalParams; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java index b47e0614ec6..934246ea221 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java @@ -143,6 +143,7 @@ public class DbClient implements ServerComponent { public ComponentDao componentDao() { return componentDao; } + public SnapshotDao snapshotDao() { return snapshotDao; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java b/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java index 86cef1b1eff..7331bfe9eb2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java @@ -29,7 +29,9 @@ import org.sonar.core.persistence.DbSession; import org.sonar.server.db.BaseDao; import org.sonar.server.search.IndexDefinition; +import java.util.Collection; import java.util.Date; +import java.util.List; import java.util.Map; public class IssueDao extends BaseDao implements DaoComponent { @@ -50,6 +52,10 @@ public class IssueDao extends BaseDao implements return mapper(session).selectByKey(key); } + protected List doGetByKeys(DbSession session, Collection keys) { + return mapper(session).selectByKeys(keys); + } + @Override protected IssueDto doUpdate(DbSession session, IssueDto issue) { mapper(session).update(issue); @@ -70,7 +76,7 @@ public class IssueDao extends BaseDao implements } @Override - protected Map getSynchronizationParams(Date date, Map params) { + protected Map getSynchronizationParams(Date date, Map params) { Map finalParams = super.getSynchronizationParams(date, params); finalParams.put(PROJECT_KEY, params.get(PROJECT_KEY)); return finalParams; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 9de0b8b32fb..48961a3403a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -234,6 +234,7 @@ public class SearchAction extends SearchRequestHandler { Set ruleKeys = newHashSet(); Set projectKeys = newHashSet(); Set componentKeys = newHashSet(); + Set componentIds = newHashSet(); Set actionPlanKeys = newHashSet(); List userLogins = newArrayList(); Map usersByLogin = newHashMap(); @@ -244,6 +245,7 @@ public class SearchAction extends SearchRequestHandler { ruleKeys.add(issue.ruleKey()); projectKeys.add(issue.projectKey()); componentKeys.add(issue.componentKey()); +// componentIds.add(issue.com()); actionPlanKeys.add(issue.actionPlanKey()); if (issue.reporter() != null) { userLogins.add(issue.reporter()); @@ -263,8 +265,10 @@ public class SearchAction extends SearchRequestHandler { usersByLogin = getUsersByLogin(session, userLogins); List componentDtos = dbClient.componentDao().getByKeys(session, componentKeys); + List subProjectDtos = dbClient.componentDao().findSubProjectsByComponentKeys(session, componentKeys); List projectDtos = dbClient.componentDao().getByKeys(session, projectKeys); + componentDtos.addAll(subProjectDtos); componentDtos.addAll(projectDtos); writeProjects(json, projectDtos); writeComponents(json, componentDtos); 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 4cad5f9e86c..beee7ab0efb 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 @@ -33,6 +33,7 @@ import org.sonar.server.exceptions.NotFoundException; import java.util.Date; import java.util.List; +import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -94,6 +95,50 @@ public class ComponentDaoTest extends AbstractDaoTestCase { assertThat(result.getAuthorizationUpdatedAt()).isEqualTo(DateUtils.parseDate("2014-06-18")); } + @Test + public void find_by_keys() { + setupData("shared"); + + List results = dao.getByKeys(session, "org.struts:struts-core:src/org/struts/RequestContext.java"); + assertThat(results).hasSize(1); + + ComponentDto result = results.get(0); + assertThat(result).isNotNull(); + assertThat(result.key()).isEqualTo("org.struts:struts-core:src/org/struts/RequestContext.java"); + assertThat(result.path()).isEqualTo("src/org/struts/RequestContext.java"); + assertThat(result.name()).isEqualTo("RequestContext.java"); + assertThat(result.longName()).isEqualTo("org.struts.RequestContext"); + assertThat(result.qualifier()).isEqualTo("FIL"); + assertThat(result.scope()).isEqualTo("FIL"); + assertThat(result.language()).isEqualTo("java"); + assertThat(result.subProjectId()).isEqualTo(2); + assertThat(result.projectId()).isEqualTo(1); + + assertThat(dao.getByKeys(session, "unknown")).isEmpty(); + } + + @Test + public void find_by_ids() { + setupData("shared"); + + List results = dao.getByIds(session, newArrayList(4L)); + assertThat(results).hasSize(1); + + ComponentDto result = results.get(0); + assertThat(result).isNotNull(); + assertThat(result.key()).isEqualTo("org.struts:struts-core:src/org/struts/RequestContext.java"); + assertThat(result.path()).isEqualTo("src/org/struts/RequestContext.java"); + assertThat(result.name()).isEqualTo("RequestContext.java"); + assertThat(result.longName()).isEqualTo("org.struts.RequestContext"); + assertThat(result.qualifier()).isEqualTo("FIL"); + assertThat(result.scope()).isEqualTo("FIL"); + assertThat(result.language()).isEqualTo("java"); + assertThat(result.subProjectId()).isEqualTo(2); + assertThat(result.projectId()).isEqualTo(1); + + assertThat(dao.getByIds(session, newArrayList(123L))).isEmpty(); + } + @Test public void get_by_id() { setupData("shared"); @@ -181,6 +226,39 @@ public class ComponentDaoTest extends AbstractDaoTestCase { assertThat(dao.getParentModuleByKey("unknown", session)).isNull(); } + @Test + public void find_sub_projects_by_component_keys() throws Exception { + setupData("multi-modules"); + + // Sub project of a file + List results = dao.findSubProjectsByComponentKeys(session, newArrayList("org.struts:struts-core:src/org/struts/RequestContext.java")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-data"); + + // Sub project of a directory + results = dao.findSubProjectsByComponentKeys(session, newArrayList("org.struts:struts-core:src/org/struts")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-data"); + + // Sub project of a sub module + results = dao.findSubProjectsByComponentKeys(session, newArrayList("org.struts:struts-data")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts"); + + // Sub project of a module + results = dao.findSubProjectsByComponentKeys(session, newArrayList("org.struts:struts-core")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts"); + + // Sub project of a project + assertThat(dao.findSubProjectsByComponentKeys(session, newArrayList("org.struts:struts"))).isEmpty(); + + // SUb projects of a component and a sub module + assertThat(dao.findSubProjectsByComponentKeys(session, newArrayList("org.struts:struts-core:src/org/struts/RequestContext.java", "org.struts:struts-data"))).hasSize(2); + + assertThat(dao.findSubProjectsByComponentKeys(session, newArrayList("unknown"))).isEmpty(); + } + @Test public void get_nullable_authorized_component_by_id() { setupData("shared"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueDaoTest.java index 962af1149af..7a4846e12cd 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueDaoTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueDaoTest.java @@ -33,6 +33,7 @@ import org.sonar.core.persistence.DbSession; import org.sonar.server.rule.RuleTesting; import java.util.Date; +import java.util.List; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -90,6 +91,43 @@ public class IssueDaoTest extends AbstractDaoTestCase { assertThat(issue.getRootComponentKey()).isEqualTo("struts"); } + @Test + public void get_by_keys() { + setupData("shared", "get_by_key"); + + List issues = dao.getByKeys(session, "ABCDE"); + assertThat(issues).hasSize(1); + + IssueDto issue = issues.get(0); + assertThat(issue.getKee()).isEqualTo("ABCDE"); + assertThat(issue.getId()).isEqualTo(100L); + assertThat(issue.getComponentId()).isEqualTo(401); + assertThat(issue.getRootComponentId()).isEqualTo(399); + assertThat(issue.getRuleId()).isEqualTo(500); + assertThat(issue.getLanguage()).isEqualTo("java"); + assertThat(issue.getSeverity()).isEqualTo("BLOCKER"); + assertThat(issue.isManualSeverity()).isFalse(); + assertThat(issue.getMessage()).isNull(); + assertThat(issue.getLine()).isEqualTo(200); + assertThat(issue.getEffortToFix()).isEqualTo(4.2); + assertThat(issue.getStatus()).isEqualTo("OPEN"); + assertThat(issue.getResolution()).isEqualTo("FIXED"); + assertThat(issue.getChecksum()).isEqualTo("XXX"); + assertThat(issue.getAuthorLogin()).isEqualTo("karadoc"); + assertThat(issue.getReporter()).isEqualTo("arthur"); + assertThat(issue.getAssignee()).isEqualTo("perceval"); + assertThat(issue.getIssueAttributes()).isEqualTo("JIRA=FOO-1234"); + assertThat(issue.getIssueCreationDate()).isNotNull(); + assertThat(issue.getIssueUpdateDate()).isNotNull(); + assertThat(issue.getIssueCloseDate()).isNotNull(); + assertThat(issue.getCreatedAt()).isNotNull(); + assertThat(issue.getUpdatedAt()).isNotNull(); + assertThat(issue.getRuleRepo()).isEqualTo("squid"); + assertThat(issue.getRule()).isEqualTo("AvoidCycle"); + assertThat(issue.getComponentKey()).isEqualTo("Action.java"); + assertThat(issue.getRootComponentKey()).isEqualTo("struts"); + } + @Test public void find_after_dates() throws Exception { setupData("shared", "some_issues"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java index e099cb17191..80bf1ddff5f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java @@ -91,7 +91,7 @@ public class SearchActionMediumTest { file = new ComponentDto() .setKey("MyComponent") - .setProjectId(project.getId()); + .setSubProjectId(project.getId()); db.componentDao().insert(session, file); db.snapshotDao().insert(session, SnapshotTesting.createForComponent(file)); @@ -233,6 +233,40 @@ public class SearchActionMediumTest { result.assertJson(this.getClass(), "issue_with_extra_fields.json", false); } + @Test + public void components_contains_sub_projects() throws Exception { + ComponentDto project = new ComponentDto() + .setKey("ProjectHavingModule") + .setScope("PRJ"); + db.componentDao().insert(session, project); + db.snapshotDao().insert(session, SnapshotTesting.createForComponent(project)); + + // project can be seen by anyone + tester.get(PermissionFacade.class).insertGroupPermission(project.getId(), DefaultGroups.ANYONE, UserRole.USER, session); + db.issueAuthorizationDao().synchronizeAfter(session, new Date(0)); + + ComponentDto module = new ComponentDto() + .setKey("ModuleHavingFile") + .setScope("PRJ") + .setSubProjectId(project.getId()); + db.componentDao().insert(session, module); + db.snapshotDao().insert(session, SnapshotTesting.createForComponent(module)); + + ComponentDto file = new ComponentDto() + .setKey("FileLinkedToModule") + .setScope("FIL") + .setSubProjectId(module.getId()); + db.componentDao().insert(session, file); + db.snapshotDao().insert(session, SnapshotTesting.createForComponent(file)); + + IssueDto issue = IssueTesting.newDto(rule, file, project); + db.issueDao().insert(session, issue); + session.commit(); + + WsTester.Result result = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION).execute(); + result.assertJson(this.getClass(), "components_contains_sub_projects.json", false); + } + @Test public void display_facets() throws Exception { IssueDto issue = IssueTesting.newDto(rule, file, project) diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/components_contains_sub_projects.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/components_contains_sub_projects.json new file mode 100644 index 00000000000..e9d286cc09a --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/components_contains_sub_projects.json @@ -0,0 +1,18 @@ +{ + "components": [ + { + "key": "FileLinkedToModule" + }, + { + "key": "ModuleHavingFile" + }, + { + "key": "ProjectHavingModule" + } + ], + "projects": [ + { + "key": "ProjectHavingModule" + } + ] +} diff --git a/sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java b/sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java index c64127cc8ba..fb22bf18d91 100644 --- a/sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java +++ b/sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java @@ -32,11 +32,13 @@ public class ComponentDto extends AuthorizedComponentDto implements Component { private String name; private String longName; private String language; - private Long projectId; private Long subProjectId; private boolean enabled = true; private Date authorizationUpdatedAt; + // Return by join for the moment + private Long projectId; + public ComponentDto setId(Long id) { super.setId(id); return this; 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 58cfdba2b5d..9102b8601d5 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 @@ -25,6 +25,7 @@ import org.sonar.core.component.ComponentDto; import javax.annotation.CheckForNull; +import java.util.Collection; import java.util.List; /** @@ -49,6 +50,15 @@ public interface ComponentMapper { */ List findModulesByProject(@Param("projectKey") String projectKey); + /** + * Return sub project of component keys + */ + List findSubProjectsByComponentKeys(@Param("keys") Collection keys); + + List findByIds(@Param("ids") Collection ids); + + List findByKeys(@Param("keys") Collection keys); + long countById(long id); @CheckForNull diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java index e09a68c1669..578df00d41d 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java @@ -34,6 +34,8 @@ public interface IssueMapper { IssueDto selectByKey(String key); + List selectByKeys(Collection keys); + /** * Return a paginated list of authorized issue ids for a user. * If the role is null, then the authorisation check is disabled. 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 c910c80e875..a2b4e19ae77 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 @@ -86,6 +86,47 @@ + + + + + + + +