]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5531 Return sub projects in components list of issues search ws
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 26 Sep 2014 08:40:08 +0000 (10:40 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 26 Sep 2014 09:02:37 +0000 (11:02 +0200)
14 files changed:
server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java
server/sonar-server/src/main/java/org/sonar/server/db/BaseDao.java
server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java
server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueDao.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueDaoTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/components_contains_sub_projects.json [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java
sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java
sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml

index fc14d1aeeb7eced979e7282fc5bfa08ea5e05aca..9834814d00402d7c828397c6ed8fd54e91c735f2 100644 (file)
@@ -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<ComponentMapper, ComponentDto, String>
     return mapper(session).findModulesByProject(projectKey);
   }
 
+  public List<ComponentDto> findSubProjectsByComponentKeys(DbSession session, Collection<String> keys) {
+    return mapper(session).findSubProjectsByComponentKeys(keys);
+  }
+
+  public List<ComponentDto> getByIds(DbSession session, Collection<Long> ids) {
+    if (ids.isEmpty()) {
+      return Collections.emptyList();
+    }
+    List<ComponentDto> components = newArrayList();
+    List<List<Long>> partitionList = Lists.partition(newArrayList(ids), 1000);
+    for (List<Long> partition : partitionList) {
+      List<ComponentDto> dtos = mapper(session).findByIds(partition);
+      components.addAll(dtos);
+    }
+    return components;
+  }
+
+  protected List<ComponentDto> doGetByKeys(DbSession session, Collection<String> keys) {
+    return mapper(session).findByKeys(keys);
+  }
+
   @CheckForNull
   public AuthorizedComponentDto getNullableAuthorizedComponentById(Long id, DbSession session) {
     return mapper(session).selectAuthorizedComponentById(id);
index 165246d5036f0a83137668efd687c6f081f452e9..388cde811a4578b89bf28a23bed36a31144a9cb8 100644 (file)
@@ -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<MAPPER, DTO extends Dto<KEY>, KEY extends Serializ
   }
 
   public List<DTO> getByKeys(DbSession session, Collection<KEY> keys) {
-    List<DTO> results = new ArrayList<DTO>();
-    for (KEY key : keys) {
-      DTO result = this.getNullableByKey(session, key);
-      if (result != null) {
-        results.add(result);
-      }
+    if (keys.isEmpty()) {
+      return Collections.emptyList();
+    }
+    List<DTO> components = newArrayList();
+    List<List<KEY>> partitionList = Lists.partition(newArrayList(keys), 1000);
+    for (List<KEY> partition : partitionList) {
+      List<DTO> dtos = doGetByKeys(session, partition);
+      components.addAll(dtos);
     }
-    return results;
+    return components;
+  }
+
+  protected List<DTO> doGetByKeys(DbSession session, Collection<KEY> keys) {
+    throw notImplemented(this);
   }
 
   @Override
@@ -348,7 +347,7 @@ public abstract class BaseDao<MAPPER, DTO extends Dto<KEY>, KEY extends Serializ
     return getSynchronizationParams(date, Collections.<String, String>emptyMap());
   }
 
-  protected Map getSynchronizationParams(Date date, Map<String, String> params) {
+  protected Map<String, Object> getSynchronizationParams(Date date, Map<String, String> params) {
     Map<String, Object> finalParams = newHashMap();
     finalParams.put("date", new Timestamp(date.getTime()));
     return finalParams;
index b47e0614ec6800b7b69269f99660efcc64fa1512..934246ea221c068d149ad980f2a4bcde7d9b686a 100644 (file)
@@ -143,6 +143,7 @@ public class DbClient implements ServerComponent {
   public ComponentDao componentDao() {
     return componentDao;
   }
+
   public SnapshotDao snapshotDao() {
     return snapshotDao;
   }
index 86cef1b1eff23a6f5b4404ea9a5f6bf464106195..7331bfe9eb21c949d7bfc472cb18f85b5ed96c4f 100644 (file)
@@ -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<IssueMapper, IssueDto, String> implements DaoComponent {
@@ -50,6 +52,10 @@ public class IssueDao extends BaseDao<IssueMapper, IssueDto, String> implements
     return mapper(session).selectByKey(key);
   }
 
+  protected List<IssueDto> doGetByKeys(DbSession session, Collection<String> 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<IssueMapper, IssueDto, String> implements
   }
 
   @Override
-  protected Map getSynchronizationParams(Date date, Map<String, String> params) {
+  protected Map<String, Object> getSynchronizationParams(Date date, Map<String, String> params) {
     Map<String, Object> finalParams = super.getSynchronizationParams(date, params);
     finalParams.put(PROJECT_KEY, params.get(PROJECT_KEY));
     return finalParams;
index 9de0b8b32fbbf0f93870a55a4558dc40227fc3d6..48961a3403adc9b7c0d03d4da2fa00c938514de1 100644 (file)
@@ -234,6 +234,7 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> {
     Set<RuleKey> ruleKeys = newHashSet();
     Set<String> projectKeys = newHashSet();
     Set<String> componentKeys = newHashSet();
+    Set<Long> componentIds = newHashSet();
     Set<String> actionPlanKeys = newHashSet();
     List<String> userLogins = newArrayList();
     Map<String, User> usersByLogin = newHashMap();
@@ -244,6 +245,7 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> {
       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<IssueQuery, Issue> {
       usersByLogin = getUsersByLogin(session, userLogins);
 
       List<ComponentDto> componentDtos = dbClient.componentDao().getByKeys(session, componentKeys);
+      List<ComponentDto> subProjectDtos = dbClient.componentDao().findSubProjectsByComponentKeys(session, componentKeys);
       List<ComponentDto> projectDtos = dbClient.componentDao().getByKeys(session, projectKeys);
 
+      componentDtos.addAll(subProjectDtos);
       componentDtos.addAll(projectDtos);
       writeProjects(json, projectDtos);
       writeComponents(json, componentDtos);
index 4cad5f9e86c9d03976708aa7175e3e101423bbf9..beee7ab0efba9839b98de01f3010a0fffc133e45 100644 (file)
@@ -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<ComponentDto> 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<ComponentDto> 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<ComponentDto> 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");
index 962af1149af1420d3c48ffacd551730416e68de3..7a4846e12cd5755ecd3092408d7a9b1af65472f1 100644 (file)
@@ -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<IssueDto> 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");
index e099cb171912016515e3e4045d9613f4d74c563d..80bf1ddff5f56cb3332ea4c1dd257badc383be41 100644 (file)
@@ -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 (file)
index 0000000..e9d286c
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "components": [
+    {
+      "key": "FileLinkedToModule"
+    },
+    {
+      "key": "ModuleHavingFile"
+    },
+    {
+      "key": "ProjectHavingModule"
+    }
+  ],
+  "projects": [
+    {
+      "key": "ProjectHavingModule"
+    }
+  ]
+}
index c64127cc8baff829845e56bf287c045a114328fd..fb22bf18d913cecd5b1b01e981c4441cf6e8b7c1 100644 (file)
@@ -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;
index 58cfdba2b5da5f5ac4a06a8a62a7c3694caa7241..9102b8601d5a006a0838d5568c2e7cbd710df5ee 100644 (file)
@@ -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<ComponentDto> findModulesByProject(@Param("projectKey") String projectKey);
 
+  /**
+   * Return sub project of component keys
+   */
+  List<ComponentDto> findSubProjectsByComponentKeys(@Param("keys") Collection<String> keys);
+
+  List<ComponentDto> findByIds(@Param("ids") Collection<Long> ids);
+
+  List<ComponentDto> findByKeys(@Param("keys") Collection<String> keys);
+
   long countById(long id);
 
   @CheckForNull
index e09a68c166957238984d40e7e0160af8bc274c0d..578df00d41d2cb327cd9834472a8b80263e1bbf5 100644 (file)
@@ -34,6 +34,8 @@ public interface IssueMapper {
 
   IssueDto selectByKey(String key);
 
+  List<IssueDto> selectByKeys(Collection<String> keys);
+
   /**
    * Return a paginated list of authorized issue ids for a user.
    * If the role is null, then the authorisation check is disabled.
index c910c80e8750d0d09ed7138c0998a27458f8aa06..a2b4e19ae7720d379c3d69ac8c75267a2c227533 100644 (file)
     </where>
   </select>
 
+  <select id="findByIds" parameterType="long" resultType="Component">
+    select <include refid="componentColumns"/>
+    from projects p
+    inner join snapshots s on s.project_id=p.id and s.islast=${_true}
+    <where>
+      p.enabled=${_true}
+      and p.id in
+      <foreach collection="ids" open="(" close=")" item="id" separator=",">
+        #{id}
+      </foreach>
+    </where>
+  </select>
+
+  <select id="findByKeys" parameterType="String" resultType="Component">
+    select <include refid="componentColumns"/>
+    from projects p
+    inner join snapshots s on s.project_id=p.id and s.islast=${_true}
+    <where>
+      p.enabled=${_true}
+      and p.kee in
+      <foreach collection="keys" open="(" close=")" item="key" separator=",">
+        #{key}
+      </foreach>
+    </where>
+  </select>
+
+  <select id="findSubProjectsByComponentKeys" parameterType="String" resultType="Component">
+    SELECT <include refid="componentColumns"/>
+    FROM projects p
+    INNER JOIN snapshots s ON s.project_id=p.id AND s.islast=${_true}
+    INNER JOIN projects child ON child.root_id=p.id AND child.enabled=${_true}
+    <where>
+      AND p.enabled=${_true}
+      AND p.scope='PRJ'
+      AND child.kee in
+      <foreach collection="keys" open="(" close=")" item="key" separator=",">
+        #{key}
+      </foreach>
+    </where>
+  </select>
+
   <select id="selectAuthorizedComponentById" parameterType="long" resultType="AuthorizedComponent">
     SELECT <include refid="authorizedComponentColumns"/>
     FROM projects p
index cd9e5cfa79da938d13a9254f667bae1f0ce7d403..ff7b9069678c04b9fd1fe7d7e48afd1e204c33df 100644 (file)
     </foreach>
   </select>
 
+  <select id="selectByKeys" parameterType="map" resultType="Issue">
+    select
+    <include refid="issueColumns"/>
+    from issues i
+    inner join rules r on r.id=i.rule_id
+    inner join projects p on p.id=i.component_id
+    inner join projects root on root.id=i.root_component_id
+    where i.kee in
+    <foreach collection="list" open="(" close=")" item="key" separator=",">
+      #{key}
+    </foreach>
+  </select>
+
   <select id="selectIssues" parameterType="map" resultType="Issue">
     select
     <include refid="issueColumns"/>