Browse Source

SONAR-9567 load issues from DB instead of ES in WS batch/issues

tags/6.6-RC1
Simon Brandhof 7 years ago
parent
commit
285daf04bf

+ 13
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java View File

import java.util.Set; import java.util.Set;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.apache.ibatis.session.ResultHandler;
import org.sonar.db.Dao; import org.sonar.db.Dao;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.RowNotFoundException; import org.sonar.db.RowNotFoundException;
import org.sonar.db.WildcardPosition;
import org.sonar.db.component.ComponentDto;


import static com.google.common.collect.FluentIterable.from; import static com.google.common.collect.FluentIterable.from;
import static org.sonar.db.DaoDatabaseUtils.buildLikeValue;
import static org.sonar.db.DatabaseUtils.executeLargeInputs; import static org.sonar.db.DatabaseUtils.executeLargeInputs;


public class IssueDao implements Dao { public class IssueDao implements Dao {
return mapper(session).selectComponentUuidsOfOpenIssuesForProjectUuid(projectUuid); return mapper(session).selectComponentUuidsOfOpenIssuesForProjectUuid(projectUuid);
} }


public void scrollNonClosedByComponentUuid(DbSession dbSession, String componentUuid, ResultHandler<IssueDto> handler) {
mapper(dbSession).selectNonClosedByComponentUuid(componentUuid, handler);
}

public void scrollNonClosedByModuleOrProject(DbSession dbSession, ComponentDto module, ResultHandler<IssueDto> handler) {
String likeModuleUuidPath = buildLikeValue(module.moduleUuidPath(), WildcardPosition.AFTER);
mapper(dbSession).scrollNonClosedByModuleOrProject(module.projectUuid(), likeModuleUuidPath, handler);
}

public void insert(DbSession session, IssueDto dto) { public void insert(DbSession session, IssueDto dto) {
mapper(session).insert(dto); mapper(session).insert(dto);
} }

+ 7
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java View File



IssueDto selectByKey(String key); IssueDto selectByKey(String key);


void selectNonClosedByComponentUuid(@Param("componentUuid") String componentUuid, ResultHandler<IssueDto> resultHandler);

Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(String projectUuid); Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(String projectUuid);


List<IssueDto> selectByKeys(List<String> keys); List<IssueDto> selectByKeys(List<String> keys);
int update(IssueDto issue); int update(IssueDto issue);


int updateIfBeforeSelectedDate(IssueDto issue); int updateIfBeforeSelectedDate(IssueDto issue);

void selectNonClosedByComponentUuid(@Param("componentUuid") String componentUuid, ResultHandler<IssueDto> handler);

void scrollNonClosedByModuleOrProject(
@Param("projectUuid") String projectUuid,
@Param("likeModuleUuidPath") String likeModuleUuidPath,
ResultHandler<IssueDto> handler);
} }

+ 14
- 1
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml View File

inner join projects p on p.uuid=i.component_uuid inner join projects p on p.uuid=i.component_uuid
inner join projects root on root.uuid=i.project_uuid inner join projects root on root.uuid=i.project_uuid
where where
i.component_uuid=#{componentUuid,jdbcType=VARCHAR} and
i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and
i.status &lt;&gt; 'CLOSED' i.status &lt;&gt; 'CLOSED'
</select> </select>


#{key,jdbcType=VARCHAR} #{key,jdbcType=VARCHAR}
</foreach> </foreach>
</select> </select>

<select id="scrollNonClosedByModuleOrProject" parameterType="map" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
select
<include refid="issueColumns"/>
from issues i
inner join rules r on r.id = i.rule_id
inner join projects p on p.uuid = i.component_uuid
inner join projects root on root.uuid = i.project_uuid
where
i.project_uuid = #{projectUuid, jdbcType=VARCHAR} and
p.module_uuid_path like #{likeModuleUuidPath, jdbcType=VARCHAR} escape '/' and
i.status &lt;&gt; 'CLOSED'
</select>
</mapper> </mapper>



+ 94
- 17
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java View File

*/ */
package org.sonar.db.issue; package org.sonar.db.issue;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting; import org.sonar.db.component.ComponentTesting;
import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleTesting; import org.sonar.db.rule.RuleTesting;


import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;


public class IssueDaoTest { public class IssueDaoTest {


private static final String ISSUE_KEY2 = "I2"; private static final String ISSUE_KEY2 = "I2";


@Rule @Rule
public ExpectedException thrown = ExpectedException.none();
public ExpectedException expectedException = ExpectedException.none();
@Rule @Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);
public DbTester db = DbTester.create(System2.INSTANCE);


private IssueDao underTest = dbTester.getDbClient().issueDao();
private IssueDao underTest = db.getDbClient().issueDao();


@Test @Test
public void selectByKeyOrFail() { public void selectByKeyOrFail() {
prepareTables(); prepareTables();


IssueDto issue = underTest.selectOrFailByKey(dbTester.getSession(), ISSUE_KEY1);
IssueDto issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
assertThat(issue.getKee()).isEqualTo(ISSUE_KEY1); assertThat(issue.getKee()).isEqualTo(ISSUE_KEY1);
assertThat(issue.getId()).isGreaterThan(0L); assertThat(issue.getId()).isGreaterThan(0L);
assertThat(issue.getComponentUuid()).isEqualTo(FILE_UUID); assertThat(issue.getComponentUuid()).isEqualTo(FILE_UUID);


@Test @Test
public void selectByKeyOrFail_fails_if_key_not_found() { public void selectByKeyOrFail_fails_if_key_not_found() {
thrown.expect(RowNotFoundException.class);
thrown.expectMessage("Issue with key 'DOES_NOT_EXIST' does not exist");
expectedException.expect(RowNotFoundException.class);
expectedException.expectMessage("Issue with key 'DOES_NOT_EXIST' does not exist");


prepareTables(); prepareTables();


underTest.selectOrFailByKey(dbTester.getSession(), "DOES_NOT_EXIST");
underTest.selectOrFailByKey(db.getSession(), "DOES_NOT_EXIST");
} }


@Test @Test
// contains I1 and I2 // contains I1 and I2
prepareTables(); prepareTables();


List<IssueDto> issues = underTest.selectByKeys(dbTester.getSession(), asList("I1", "I2", "I3"));
List<IssueDto> issues = underTest.selectByKeys(db.getSession(), asList("I1", "I2", "I3"));
// results are not ordered, so do not use "containsExactly" // results are not ordered, so do not use "containsExactly"
assertThat(issues).extracting("key").containsOnly("I1", "I2"); assertThat(issues).extracting("key").containsOnly("I1", "I2");
} }
// contains I1 and I2 // contains I1 and I2
prepareTables(); prepareTables();


Iterable<IssueDto> issues = underTest.selectByOrderedKeys(dbTester.getSession(), asList("I1", "I2", "I3"));
Iterable<IssueDto> issues = underTest.selectByOrderedKeys(db.getSession(), asList("I1", "I2", "I3"));
assertThat(issues).extracting("key").containsExactly("I1", "I2"); assertThat(issues).extracting("key").containsExactly("I1", "I2");


issues = underTest.selectByOrderedKeys(dbTester.getSession(), asList("I2", "I3", "I1"));
issues = underTest.selectByOrderedKeys(db.getSession(), asList("I2", "I3", "I1"));
assertThat(issues).extracting("key").containsExactly("I2", "I1"); assertThat(issues).extracting("key").containsExactly("I2", "I1");
} }


@Test
public void scrollNonClosedByComponentUuid() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(newFileDto(project));
IssueDto openIssue1OnFile = db.issues().insert(rule, project, file, i -> i.setStatus("OPEN").setResolution(null));
IssueDto openIssue2OnFile = db.issues().insert(rule, project, file, i -> i.setStatus("OPEN").setResolution(null));
IssueDto closedIssueOnFile = db.issues().insert(rule, project, file, i -> i.setStatus("CLOSED").setResolution("FIXED"));
IssueDto openIssueOnProject = db.issues().insert(rule, project, project, i -> i.setStatus("OPEN").setResolution(null));

Accumulator accumulator = new Accumulator();
underTest.scrollNonClosedByComponentUuid(db.getSession(), file.uuid(), accumulator);
accumulator.assertThatContainsOnly(openIssue1OnFile, openIssue2OnFile);

accumulator.clear();
underTest.scrollNonClosedByComponentUuid(db.getSession(), project.uuid(), accumulator);
accumulator.assertThatContainsOnly(openIssueOnProject);

accumulator.clear();
underTest.scrollNonClosedByComponentUuid(db.getSession(), "does_not_exist", accumulator);
assertThat(accumulator.list).isEmpty();
}

@Test
public void scrollNonClosedByModuleOrProject() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto anotherProject = db.components().insertPrivateProject();
ComponentDto module = db.components().insertComponent(newModuleDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(module));
IssueDto openIssue1OnFile = db.issues().insert(rule, project, file, i -> i.setStatus("OPEN").setResolution(null));
IssueDto openIssue2OnFile = db.issues().insert(rule, project, file, i -> i.setStatus("OPEN").setResolution(null));
IssueDto closedIssueOnFile = db.issues().insert(rule, project, file, i -> i.setStatus("CLOSED").setResolution("FIXED"));
IssueDto openIssueOnModule = db.issues().insert(rule, project, module, i -> i.setStatus("OPEN").setResolution(null));
IssueDto openIssueOnProject = db.issues().insert(rule, project, project, i -> i.setStatus("OPEN").setResolution(null));
IssueDto openIssueOnAnotherProject = db.issues().insert(rule, anotherProject, anotherProject, i -> i.setStatus("OPEN").setResolution(null));

Accumulator accumulator = new Accumulator();
underTest.scrollNonClosedByModuleOrProject(db.getSession(), project, accumulator);
accumulator.assertThatContainsOnly(openIssue1OnFile, openIssue2OnFile, openIssueOnModule, openIssueOnProject);

accumulator.clear();
underTest.scrollNonClosedByModuleOrProject(db.getSession(), module, accumulator);
accumulator.assertThatContainsOnly(openIssue1OnFile, openIssue2OnFile, openIssueOnModule);

accumulator.clear();
ComponentDto notPersisted = ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization());
underTest.scrollNonClosedByModuleOrProject(db.getSession(), notPersisted, accumulator);
assertThat(accumulator.list).isEmpty();
}

private static IssueDto newIssueDto(String key) { private static IssueDto newIssueDto(String key) {
IssueDto dto = new IssueDto(); IssueDto dto = new IssueDto();
dto.setComponent(new ComponentDto().setKey("struts:Action").setId(123L).setUuid("component-uuid")); dto.setComponent(new ComponentDto().setKey("struts:Action").setId(123L).setUuid("component-uuid"));
} }


private void prepareTables() { private void prepareTables() {
dbTester.rules().insertRule(RULE);
OrganizationDto organizationDto = dbTester.organizations().insert();
ComponentDto projectDto = dbTester.components().insertPrivateProject(organizationDto, (t) -> t.setUuid(PROJECT_UUID).setKey(PROJECT_KEY));
dbTester.components().insertComponent(ComponentTesting.newFileDto(projectDto).setUuid(FILE_UUID).setKey(FILE_KEY));
underTest.insert(dbTester.getSession(), newIssueDto(ISSUE_KEY1)
db.rules().insertRule(RULE);
OrganizationDto organizationDto = db.organizations().insert();
ComponentDto projectDto = db.components().insertPrivateProject(organizationDto, (t) -> t.setUuid(PROJECT_UUID).setKey(PROJECT_KEY));
db.components().insertComponent(newFileDto(projectDto).setUuid(FILE_UUID).setKey(FILE_KEY));
underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY1)
.setMessage("the message") .setMessage("the message")
.setRuleId(RULE.getId()) .setRuleId(RULE.getId())
.setComponentUuid(FILE_UUID) .setComponentUuid(FILE_UUID)
.setProjectUuid(PROJECT_UUID)); .setProjectUuid(PROJECT_UUID));
underTest.insert(dbTester.getSession(), newIssueDto(ISSUE_KEY2)
underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2)
.setRuleId(RULE.getId()) .setRuleId(RULE.getId())
.setComponentUuid(FILE_UUID) .setComponentUuid(FILE_UUID)
.setProjectUuid(PROJECT_UUID)); .setProjectUuid(PROJECT_UUID));
dbTester.getSession().commit();
db.getSession().commit();
}

private static class Accumulator implements ResultHandler<IssueDto> {
private final List<IssueDto> list = new ArrayList<>();

private void clear() {
list.clear();
}

@Override
public void handleResult(ResultContext<? extends IssueDto> resultContext) {
list.add(resultContext.getResultObject());
}

private void assertThatContainsOnly(IssueDto... issues) {
assertThat(list)
.extracting(IssueDto::getKey)
.containsExactlyInAnyOrder(Arrays.stream(issues).map(IssueDto::getKey).toArray(String[]::new));
}
} }
} }

+ 50
- 29
server/sonar-server/src/main/java/org/sonar/server/batch/IssuesAction.java View File

*/ */
package org.sonar.server.batch; package org.sonar.server.batch;


import com.google.common.base.Splitter;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.ibatis.session.ResultHandler;
import org.sonar.api.resources.Scopes; import org.sonar.api.resources.Scopes;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.scanner.protocol.input.ScannerInput; import org.sonar.scanner.protocol.input.ScannerInput;
import org.sonar.server.component.ComponentFinder; import org.sonar.server.component.ComponentFinder;
import org.sonar.server.issue.index.IssueDoc;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.user.UserSession; import org.sonar.server.user.UserSession;
import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.MediaTypes;


import static com.google.common.collect.Maps.newHashMap; import static com.google.common.collect.Maps.newHashMap;
import static java.lang.String.format;
import static org.sonar.api.web.UserRole.USER; import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
public class IssuesAction implements BatchWsAction { public class IssuesAction implements BatchWsAction {


private static final String PARAM_KEY = "key"; private static final String PARAM_KEY = "key";
private static final Splitter MODULE_PATH_SPLITTER = Splitter.on('.').trimResults().omitEmptyStrings();


private final DbClient dbClient; private final DbClient dbClient;
private final IssueIndex issueIndex;
private final UserSession userSession; private final UserSession userSession;
private final ComponentFinder componentFinder; private final ComponentFinder componentFinder;


public IssuesAction(DbClient dbClient, IssueIndex issueIndex, UserSession userSession, ComponentFinder componentFinder) {
public IssuesAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) {
this.dbClient = dbClient; this.dbClient = dbClient;
this.issueIndex = issueIndex;
this.userSession = userSession; this.userSession = userSession;
this.componentFinder = componentFinder; this.componentFinder = componentFinder;
} }


@Override @Override
public void handle(Request request, Response response) throws Exception { public void handle(Request request, Response response) throws Exception {
response.stream().setMediaType(MediaTypes.PROTOBUF);

try (DbSession session = dbClient.openSession(false)) {
try (DbSession dbSession = dbClient.openSession(false)) {
String componentKey = request.mandatoryParam(PARAM_KEY); String componentKey = request.mandatoryParam(PARAM_KEY);
ComponentDto component = componentFinder.getByKey(session, componentKey);
ComponentDto component = componentFinder.getByKey(dbSession, componentKey);
userSession.checkComponentPermission(USER, component); userSession.checkComponentPermission(USER, component);


Map<String, String> keysByUUid = keysByUUid(session, component);
Map<String, String> keysByUUid = keysByUUid(dbSession, component);

ScannerInput.ServerIssue.Builder responseBuilder = ScannerInput.ServerIssue.newBuilder();
response.stream().setMediaType(MediaTypes.PROTOBUF);
OutputStream output = response.stream().output();


ScannerInput.ServerIssue.Builder issueBuilder = ScannerInput.ServerIssue.newBuilder();
for (Iterator<IssueDoc> issueDocIterator = issueIndex.selectIssuesForBatch(component); issueDocIterator.hasNext();) {
handleIssue(issueDocIterator.next(), issueBuilder, keysByUUid, response.stream().output());
ResultHandler<IssueDto> handler = resultContext -> {
IssueDto issue = resultContext.getResultObject();
handleIssue(issue, responseBuilder, keysByUUid, output);
};
switch (component.scope()) {
case Scopes.PROJECT:
dbClient.issueDao().scrollNonClosedByModuleOrProject(dbSession, component, handler);
break;
case Scopes.FILE:
dbClient.issueDao().scrollNonClosedByComponentUuid(dbSession, component.uuid(), handler);
break;
default:
// only projects, modules and files are supported. Other types of components are not allowed.
throw new IllegalStateException(format("Component of scope '%s' is not allowed", component.scope()));
} }
} }
} }


private static void handleIssue(IssueDoc issue, ScannerInput.ServerIssue.Builder issueBuilder, Map<String, String> keysByUUid, OutputStream out) {
issueBuilder.setKey(issue.key());
issueBuilder.setModuleKey(keysByUUid.get(issue.moduleUuid()));
setNullable(issue.filePath(), issueBuilder::setPath);
issueBuilder.setRuleRepository(issue.ruleKey().repository());
issueBuilder.setRuleKey(issue.ruleKey().rule());
setNullable(issue.checksum(), issueBuilder::setChecksum);
setNullable(issue.assignee(), issueBuilder::setAssigneeLogin);
setNullable(issue.line(), issueBuilder::setLine);
setNullable(issue.message(), issueBuilder::setMsg);
issueBuilder.setSeverity(org.sonar.scanner.protocol.Constants.Severity.valueOf(issue.severity()));
private static void handleIssue(IssueDto issue, ScannerInput.ServerIssue.Builder issueBuilder,
Map<String, String> keysByUUid, OutputStream out) {
issueBuilder.setKey(issue.getKey());
String moduleUuid = extractModuleUuid(issue);
issueBuilder.setModuleKey(keysByUUid.get(moduleUuid));
setNullable(issue.getFilePath(), issueBuilder::setPath);
issueBuilder.setRuleRepository(issue.getRuleRepo());
issueBuilder.setRuleKey(issue.getRule());
setNullable(issue.getChecksum(), issueBuilder::setChecksum);
setNullable(issue.getAssignee(), issueBuilder::setAssigneeLogin);
setNullable(issue.getLine(), issueBuilder::setLine);
setNullable(issue.getMessage(), issueBuilder::setMsg);
issueBuilder.setSeverity(org.sonar.scanner.protocol.Constants.Severity.valueOf(issue.getSeverity()));
issueBuilder.setManualSeverity(issue.isManualSeverity()); issueBuilder.setManualSeverity(issue.isManualSeverity());
issueBuilder.setStatus(issue.status());
setNullable(issue.resolution(), issueBuilder::setResolution);
issueBuilder.setType(issue.type().name());
issueBuilder.setCreationDate(issue.creationDate().getTime());
issueBuilder.setStatus(issue.getStatus());
setNullable(issue.getResolution(), issueBuilder::setResolution);
issueBuilder.setType(RuleType.valueOf(issue.getType()).name());
issueBuilder.setCreationDate(issue.getIssueCreationTime());
try { try {
issueBuilder.build().writeDelimitedTo(out); issueBuilder.build().writeDelimitedTo(out);
} catch (IOException e) { } catch (IOException e) {
issueBuilder.clear(); issueBuilder.clear();
} }


private static String extractModuleUuid(IssueDto issue) {
List<String> split = MODULE_PATH_SPLITTER.splitToList(issue.getModuleUuidPath());
return split.get(split.size()-1);
}

private Map<String, String> keysByUUid(DbSession session, ComponentDto component) { private Map<String, String> keysByUUid(DbSession session, ComponentDto component) {
Map<String, String> keysByUUid = newHashMap(); Map<String, String> keysByUUid = newHashMap();
if (Scopes.PROJECT.equals(component.scope())) { if (Scopes.PROJECT.equals(component.scope())) {

+ 0
- 44
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java View File

import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.metrics.min.Min; import org.elasticsearch.search.aggregations.metrics.min.Min;
import org.elasticsearch.search.aggregations.metrics.sum.SumBuilder; import org.elasticsearch.search.aggregations.metrics.sum.SumBuilder;
import org.joda.time.Duration; import org.joda.time.Duration;
import org.sonar.api.issue.Issue;
import org.sonar.api.resources.Scopes;
import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.core.util.stream.MoreCollectors; import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.es.EsClient; import org.sonar.server.es.EsClient;
import org.sonar.server.es.EsUtils; import org.sonar.server.es.EsUtils;
} }
return boolQuery; return boolQuery;
} }

/**
* Return non closed issues for a given project, module, or file. Other kind of components are not allowed.
* Only fields needed for the batch are returned.
*/
public Iterator<IssueDoc> selectIssuesForBatch(ComponentDto component) {
BoolQueryBuilder filter = boolQuery()
.must(createAuthorizationFilter(true))
.mustNot(termsQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_CLOSED));

switch (component.scope()) {
case Scopes.PROJECT:
filter.must(termsQuery(IssueIndexDefinition.FIELD_ISSUE_MODULE_PATH, component.uuid()));
break;
case Scopes.FILE:
filter.must(termsQuery(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, component.uuid()));
break;
default:
throw new IllegalStateException(format("Component of scope '%s' is not allowed", component.scope()));
}

SearchRequestBuilder requestBuilder = client
.prepareSearch(INDEX_TYPE_ISSUE)
.setSearchType(SearchType.SCAN)
.setScroll(TimeValue.timeValueMinutes(EsUtils.SCROLL_TIME_IN_MINUTES))
.setSize(10_000)
.setFetchSource(
new String[] {IssueIndexDefinition.FIELD_ISSUE_KEY, IssueIndexDefinition.FIELD_ISSUE_RULE_KEY, IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID,
IssueIndexDefinition.FIELD_ISSUE_FILE_PATH, IssueIndexDefinition.FIELD_ISSUE_SEVERITY, IssueIndexDefinition.FIELD_ISSUE_MANUAL_SEVERITY,
IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, IssueIndexDefinition.FIELD_ISSUE_STATUS, IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE,
IssueIndexDefinition.FIELD_ISSUE_LINE, IssueIndexDefinition.FIELD_ISSUE_MESSAGE, IssueIndexDefinition.FIELD_ISSUE_CHECKSUM,
IssueIndexDefinition.FIELD_ISSUE_TYPE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT},
null)
.setQuery(boolQuery().must(matchAllQuery()).filter(filter));
SearchResponse response = requestBuilder.get();

return EsUtils.scroll(client, response.getScrollId(), IssueDoc::new);
}
} }

+ 165
- 228
server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java View File

*/ */
package org.sonar.server.batch; package org.sonar.server.batch;


import com.google.common.base.Throwables;
import java.io.IOException; import java.io.IOException;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole; import org.sonar.api.web.UserRole;
import org.sonar.core.util.CloseableIterator;
import org.sonar.core.util.Protobuf;
import org.sonar.db.DbTester; import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.scanner.protocol.Constants.Severity; import org.sonar.scanner.protocol.Constants.Severity;
import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue;
import org.sonar.server.component.TestComponentFinder; import org.sonar.server.component.TestComponentFinder;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.issue.index.IssueIteratorFactory;
import org.sonar.server.permission.index.AuthorizationTypeSupport;
import org.sonar.server.permission.index.PermissionIndexerTester;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule; import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester; import org.sonar.server.ws.WsActionTester;


import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.rules.RuleType.BUG; import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto; import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.db.rule.RuleTesting.newRule;


public class IssuesActionTest { public class IssuesActionTest {


private static final String PROJECT_KEY = "struts";
private static final String PROJECT_UUID = "ABCD";
private static final String MODULE_KEY = "struts-core";
private static final String MODULE_UUID = "BCDE";
private final static String FILE_KEY = "Action.java";
private static final String FILE_UUID = "CDEF";

private System2 system2 = System2.INSTANCE; private System2 system2 = System2.INSTANCE;


@Rule @Rule
public ExpectedException thrown = ExpectedException.none();
public ExpectedException expectedException = ExpectedException.none();
@Rule @Rule
public DbTester db = DbTester.create(system2); public DbTester db = DbTester.create(system2);
@Rule @Rule
public EsTester es = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone(); public UserSessionRule userSessionRule = UserSessionRule.standalone();


private static RuleDefinitionDto RULE_DEFINITION = newRule(RuleKey.of("squid", "AvoidCycle"));

private IssueIndexer issueIndexer = new IssueIndexer(es.client(), new IssueIteratorFactory(db.getDbClient()));
private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, issueIndexer);
private WsActionTester tester = new WsActionTester(new IssuesAction(db.getDbClient(),
new IssueIndex(es.client(), system2, userSessionRule, new AuthorizationTypeSupport(userSessionRule)),
userSessionRule, TestComponentFinder.from(db)));
private WsActionTester tester = new WsActionTester(new IssuesAction(db.getDbClient(), userSessionRule, TestComponentFinder.from(db)));


@Test @Test
public void return_minimal_fields() throws Exception {
ComponentDto project = db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setKey(PROJECT_KEY));
ComponentDto module = db.components().insertComponent(newModuleDto(MODULE_UUID, project).setKey(MODULE_KEY));
ComponentDto file = db.components().insertComponent(newFileDto(module, null, FILE_UUID).setKey(FILE_KEY).setPath(null));
db.rules().insert(RULE_DEFINITION);
db.issues().insert(RULE_DEFINITION, project, file, issue -> issue
.setKee("EFGH")
.setSeverity("BLOCKER")
.setStatus("RESOLVED")
.setType(BUG)
.setResolution(null)
.setManualSeverity(false)
.setMessage(null)
.setLine(null)
.setChecksum(null)
.setAssignee(null));
indexIssues(project);
addBrowsePermissionOnComponent(project);

ServerIssue serverIssue = call(PROJECT_KEY);

assertThat(serverIssue.getKey()).isEqualTo("EFGH");
assertThat(serverIssue.getModuleKey()).isEqualTo(MODULE_KEY);
public void test_nullable_fields() throws Exception {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto module = db.components().insertComponent(newModuleDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(module, null).setPath(null));
IssueDto issue = db.issues().insert(rule, project, file, i ->
i.setSeverity("BLOCKER")
// non-null fields
.setStatus("OPEN")
.setType(BUG)
.setManualSeverity(true)

// all the nullable fields
.setResolution(null)
.setMessage(null)
.setLine(null)
.setChecksum(null)
.setAssignee(null));
addPermissionTo(project);

ServerIssue serverIssue = call(project.key());

assertThat(serverIssue.getKey()).isEqualTo(issue.getKey());
assertThat(serverIssue.getModuleKey()).isEqualTo(module.getKey());
assertThat(serverIssue.getRuleRepository()).isEqualTo(rule.getRepositoryKey());
assertThat(serverIssue.getRuleKey()).isEqualTo(rule.getRuleKey());
assertThat(serverIssue.getStatus()).isEqualTo("OPEN");
assertThat(serverIssue.getSeverity()).isEqualTo(Severity.BLOCKER);
assertThat(serverIssue.getType()).isEqualTo(BUG.name());
assertThat(serverIssue.getManualSeverity()).isTrue();

assertThat(serverIssue.hasPath()).isFalse(); assertThat(serverIssue.hasPath()).isFalse();
assertThat(serverIssue.getRuleRepository()).isEqualTo("squid");
assertThat(serverIssue.getRuleKey()).isEqualTo("AvoidCycle");
assertThat(serverIssue.hasLine()).isFalse(); assertThat(serverIssue.hasLine()).isFalse();
assertThat(serverIssue.hasMsg()).isFalse(); assertThat(serverIssue.hasMsg()).isFalse();
assertThat(serverIssue.hasResolution()).isFalse(); assertThat(serverIssue.hasResolution()).isFalse();
assertThat(serverIssue.getStatus()).isEqualTo("RESOLVED");
assertThat(serverIssue.getSeverity()).isEqualTo(Severity.BLOCKER);
assertThat(serverIssue.getType()).isEqualTo(BUG.name());
assertThat(serverIssue.getManualSeverity()).isFalse();
assertThat(serverIssue.hasChecksum()).isFalse(); assertThat(serverIssue.hasChecksum()).isFalse();
assertThat(serverIssue.hasAssigneeLogin()).isFalse(); assertThat(serverIssue.hasAssigneeLogin()).isFalse();
} }


@Test @Test
public void issues_from_project() throws Exception {
OrganizationDto organizationDto = db.organizations().insert();
ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organizationDto, PROJECT_UUID).setKey(PROJECT_KEY));
ComponentDto module = db.components().insertComponent(newModuleDto(MODULE_UUID, project).setKey(MODULE_KEY));
ComponentDto file = db.components().insertComponent(newFileDto(module, null, FILE_UUID).setKey(FILE_KEY).setPath("src/org/struts/Action.java"));
db.rules().insert(RULE_DEFINITION);
db.issues().insert(RULE_DEFINITION, project, file, issue -> issue
.setKee("EFGH")
.setSeverity("BLOCKER")
.setStatus("RESOLVED")
.setResolution("FALSE-POSITIVE")
.setManualSeverity(false)
.setMessage("Do not use this method")
.setLine(200)
.setChecksum("123456")
.setAssignee("john"));
indexIssues(project);
addBrowsePermissionOnComponent(project);

ServerIssue serverIssue = call(PROJECT_KEY);

assertThat(serverIssue.getKey()).isEqualTo("EFGH");
assertThat(serverIssue.getModuleKey()).isEqualTo(MODULE_KEY);
assertThat(serverIssue.getPath()).isEqualTo("src/org/struts/Action.java");
assertThat(serverIssue.getRuleRepository()).isEqualTo("squid");
assertThat(serverIssue.getRuleKey()).isEqualTo("AvoidCycle");
assertThat(serverIssue.getLine()).isEqualTo(200);
assertThat(serverIssue.getMsg()).isEqualTo("Do not use this method");
assertThat(serverIssue.getResolution()).isEqualTo("FALSE-POSITIVE");
assertThat(serverIssue.getStatus()).isEqualTo("RESOLVED");
public void test_fields_with_non_null_values() throws Exception {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto module = db.components().insertComponent(newModuleDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(module, null));
IssueDto issue = db.issues().insert(rule, project, file, i ->
i.setSeverity("BLOCKER")
.setStatus("OPEN")
.setType(BUG)
.setManualSeverity(true)
.setResolution("FIXED")
.setMessage("the message")
.setLine(10)
.setChecksum("ABC")
.setAssignee("foo"));
addPermissionTo(project);

ServerIssue serverIssue = call(project.key());

assertThat(serverIssue.getKey()).isEqualTo(issue.getKey());
assertThat(serverIssue.getModuleKey()).isEqualTo(module.getKey());
assertThat(serverIssue.getRuleRepository()).isEqualTo(rule.getRepositoryKey());
assertThat(serverIssue.getRuleKey()).isEqualTo(rule.getRuleKey());
assertThat(serverIssue.getStatus()).isEqualTo("OPEN");
assertThat(serverIssue.getSeverity()).isEqualTo(Severity.BLOCKER); assertThat(serverIssue.getSeverity()).isEqualTo(Severity.BLOCKER);
assertThat(serverIssue.getManualSeverity()).isFalse();
assertThat(serverIssue.getChecksum()).isEqualTo("123456");
assertThat(serverIssue.getAssigneeLogin()).isEqualTo("john");
assertThat(serverIssue.getType()).isEqualTo(BUG.name());
assertThat(serverIssue.getManualSeverity()).isTrue();
assertThat(serverIssue.getPath()).isEqualTo(file.path());
assertThat(serverIssue.getLine()).isEqualTo(issue.getLine());
assertThat(serverIssue.getMsg()).isEqualTo(issue.getMessage());
assertThat(serverIssue.getResolution()).isEqualTo(issue.getResolution());
assertThat(serverIssue.getChecksum()).isEqualTo(issue.getChecksum());
assertThat(serverIssue.getAssigneeLogin()).isEqualTo(issue.getAssignee());
} }


@Test @Test
public void issues_from_module() throws Exception {
ComponentDto project = db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setKey(PROJECT_KEY));
ComponentDto module = db.components().insertComponent(newModuleDto(MODULE_UUID, project).setKey(MODULE_KEY));
ComponentDto file = db.components().insertComponent(newFileDto(module, null, FILE_UUID).setKey(FILE_KEY).setPath("src/org/struts/Action.java"));
db.rules().insert(RULE_DEFINITION);
db.issues().insert(RULE_DEFINITION, project, file, issue -> issue
.setKee("EFGH")
.setSeverity("BLOCKER")
.setStatus("RESOLVED")
.setResolution("FALSE-POSITIVE")
.setManualSeverity(false)
.setMessage("Do not use this method")
.setLine(200)
.setChecksum("123456")
.setAssignee("john"));
indexIssues(project);
addBrowsePermissionOnComponent(project);

ServerIssue serverIssue = call(PROJECT_KEY);

assertThat(serverIssue.getKey()).isEqualTo("EFGH");
assertThat(serverIssue.getModuleKey()).isEqualTo(MODULE_KEY);
assertThat(serverIssue.getPath()).isEqualTo("src/org/struts/Action.java");
assertThat(serverIssue.getRuleRepository()).isEqualTo("squid");
assertThat(serverIssue.getRuleKey()).isEqualTo("AvoidCycle");
assertThat(serverIssue.getLine()).isEqualTo(200);
assertThat(serverIssue.getMsg()).isEqualTo("Do not use this method");
assertThat(serverIssue.getResolution()).isEqualTo("FALSE-POSITIVE");
assertThat(serverIssue.getStatus()).isEqualTo("RESOLVED");
assertThat(serverIssue.getSeverity()).isEqualTo(Severity.BLOCKER);
assertThat(serverIssue.getManualSeverity()).isFalse();
assertThat(serverIssue.getChecksum()).isEqualTo("123456");
assertThat(serverIssue.getAssigneeLogin()).isEqualTo("john");
public void return_issues_of_project() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto module = db.components().insertComponent(newModuleDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(module, null));
IssueDto issueOnFile = db.issues().insert(rule, project, file, i -> i.setKee("ON_FILE"));
IssueDto issueOnModule = db.issues().insert(rule, project, module, i -> i.setKee("ON_MODULE"));
IssueDto issueOnProject = db.issues().insert(rule, project, project, i -> i.setKee("ON_PROJECT"));

addPermissionTo(project);
try (CloseableIterator<ServerIssue> result = callStream(project.key())) {
assertThat(result)
.extracting(ServerIssue::getKey, ServerIssue::getModuleKey)
.containsExactlyInAnyOrder(
tuple(issueOnFile.getKey(), module.key()),
tuple(issueOnModule.getKey(), module.key()),
tuple(issueOnProject.getKey(), project.key()));
}
} }


@Test @Test
public void issues_from_file() throws Exception {
ComponentDto project = db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setKey(PROJECT_KEY));
ComponentDto module = db.components().insertComponent(newModuleDto(MODULE_UUID, project).setKey(MODULE_KEY));
ComponentDto file = db.components().insertComponent(newFileDto(module, null, FILE_UUID).setKey(FILE_KEY).setPath("src/org/struts/Action.java"));
db.rules().insert(RULE_DEFINITION);
db.issues().insert(RULE_DEFINITION, project, file, issue -> issue
.setKee("EFGH")
.setSeverity("BLOCKER")
.setStatus("RESOLVED")
.setResolution("FALSE-POSITIVE")
.setManualSeverity(false)
.setMessage("Do not use this method")
.setLine(200)
.setChecksum("123456")
.setAssignee("john"));
indexIssues(project);
addBrowsePermissionOnComponent(project);

ServerIssue serverIssue = call(FILE_KEY);

assertThat(serverIssue.getKey()).isEqualTo("EFGH");
assertThat(serverIssue.getModuleKey()).isEqualTo(MODULE_KEY);
assertThat(serverIssue.getPath()).isEqualTo("src/org/struts/Action.java");
assertThat(serverIssue.getRuleRepository()).isEqualTo("squid");
assertThat(serverIssue.getRuleKey()).isEqualTo("AvoidCycle");
assertThat(serverIssue.getLine()).isEqualTo(200);
assertThat(serverIssue.getMsg()).isEqualTo("Do not use this method");
assertThat(serverIssue.getResolution()).isEqualTo("FALSE-POSITIVE");
assertThat(serverIssue.getStatus()).isEqualTo("RESOLVED");
assertThat(serverIssue.getSeverity()).isEqualTo(Severity.BLOCKER);
assertThat(serverIssue.getManualSeverity()).isFalse();
assertThat(serverIssue.getChecksum()).isEqualTo("123456");
assertThat(serverIssue.getAssigneeLogin()).isEqualTo("john");
public void return_issues_of_module() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto module = db.components().insertComponent(newModuleDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(module, null));
IssueDto issueOnFile = db.issues().insert(rule, project, file, i -> i.setKee("ON_FILE"));
IssueDto issueOnModule = db.issues().insert(rule, project, module, i -> i.setKee("ON_MODULE"));
IssueDto issueOnProject = db.issues().insert(rule, project, project, i -> i.setKee("ON_PROJECT"));

addPermissionTo(project);
try (CloseableIterator<ServerIssue> result = callStream(module.key())) {
assertThat(result)
.extracting(ServerIssue::getKey, ServerIssue::getModuleKey)
.containsExactlyInAnyOrder(
tuple(issueOnFile.getKey(), module.key()),
tuple(issueOnModule.getKey(), module.key()));
}
} }


@Test @Test
public void issues_attached_on_module() throws Exception {
OrganizationDto organizationDto = db.organizations().insert();
ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organizationDto, PROJECT_UUID).setKey(PROJECT_KEY));
ComponentDto module = db.components().insertComponent(newModuleDto(MODULE_UUID, project).setKey(MODULE_KEY));
db.rules().insert(RULE_DEFINITION);
db.issues().insert(RULE_DEFINITION, project, module, issue -> issue
.setKee("EFGH")
.setSeverity("BLOCKER")
.setStatus("RESOLVED")
.setResolution("FALSE-POSITIVE")
.setManualSeverity(false)
.setMessage("Do not use this method")
.setLine(200)
.setChecksum("123456")
.setAssignee("john"));
indexIssues(project);
addBrowsePermissionOnComponent(project);

ServerIssue serverIssue = call(MODULE_KEY);

assertThat(serverIssue.getKey()).isEqualTo("EFGH");
assertThat(serverIssue.getModuleKey()).isEqualTo(MODULE_KEY);
assertThat(serverIssue.hasPath()).isFalse();
assertThat(serverIssue.getRuleRepository()).isEqualTo("squid");
assertThat(serverIssue.getRuleKey()).isEqualTo("AvoidCycle");
assertThat(serverIssue.getLine()).isEqualTo(200);
assertThat(serverIssue.getMsg()).isEqualTo("Do not use this method");
assertThat(serverIssue.getResolution()).isEqualTo("FALSE-POSITIVE");
assertThat(serverIssue.getStatus()).isEqualTo("RESOLVED");
assertThat(serverIssue.getSeverity()).isEqualTo(Severity.BLOCKER);
assertThat(serverIssue.getManualSeverity()).isFalse();
assertThat(serverIssue.getChecksum()).isEqualTo("123456");
assertThat(serverIssue.getAssigneeLogin()).isEqualTo("john");
public void return_issues_of_file() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto module = db.components().insertComponent(newModuleDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(module, null));
IssueDto issueOnFile = db.issues().insert(rule, project, file);
IssueDto issueOnModule = db.issues().insert(rule, project, module);
IssueDto issueOnProject = db.issues().insert(rule, project, project);

addPermissionTo(project);
try (CloseableIterator<ServerIssue> result = callStream(file.key())) {
assertThat(result)
.extracting(ServerIssue::getKey, ServerIssue::getModuleKey)
.containsExactlyInAnyOrder(
tuple(issueOnFile.getKey(), module.key()));
}
} }


@Test @Test
public void project_issues_attached_file_on_removed_module() throws Exception {
ComponentDto project = db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setKey(PROJECT_KEY));
// File and module are removed
ComponentDto module = db.components().insertComponent(newModuleDto(MODULE_UUID, project).setKey(MODULE_KEY).setEnabled(false));
ComponentDto file = db.components().insertComponent(newFileDto(module, null, FILE_UUID).setKey(FILE_KEY).setPath("src/org/struts/Action.java").setEnabled(false));
db.rules().insert(RULE_DEFINITION);
db.issues().insert(RULE_DEFINITION, project, file, issue -> issue
.setKee("EFGH")
.setSeverity("BLOCKER")
.setStatus("RESOLVED")
.setResolution("FALSE-POSITIVE")
.setManualSeverity(false)
.setMessage("Do not use this method")
.setLine(200)
.setChecksum("123456")
.setAssignee("john"));
indexIssues(project);
addBrowsePermissionOnComponent(project);

ServerIssue serverIssue = call(PROJECT_KEY);

assertThat(serverIssue.getKey()).isEqualTo("EFGH");
// Module key of removed file should be returned
assertThat(serverIssue.getModuleKey()).isEqualTo(MODULE_KEY);
public void fail_if_requested_component_is_a_directory() throws IOException {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto directory = db.components().insertComponent(newDirectory(project, "src/main/java"));
addPermissionTo(project);

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Component of scope 'DIR' is not allowed");

call(directory.key());
} }


@Test @Test
public void fail_without_browse_permission_on_file() throws Exception {
public void issues_on_disabled_modules_are_returned() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto module = db.components().insertComponent(newModuleDto(project).setEnabled(false));
ComponentDto file = db.components().insertComponent(newFileDto(module, null).setEnabled(false));
IssueDto issue = db.issues().insert(rule, project, file);

addPermissionTo(project);
try (CloseableIterator<ServerIssue> result = callStream(project.key())) {
// Module key of removed file should be returned
assertThat(result)
.extracting(ServerIssue::getKey, ServerIssue::getModuleKey)
.containsExactly(tuple(issue.getKey(), module.key()));
}
}

@Test
public void fail_if_user_does_not_have_permission_on_project() {
ComponentDto project = db.components().insertPrivateProject(); ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(newFileDto(project)); ComponentDto file = db.components().insertComponent(newFileDto(project));


thrown.expect(ForbiddenException.class);
expectedException.expect(ForbiddenException.class);


tester.newRequest().setParam("key", file.key()).execute(); tester.newRequest().setParam("key", file.key()).execute();
} }


private void indexIssues(ComponentDto project) {
issueIndexer.indexOnStartup(null);
authorizationIndexerTester.allowOnlyAnyone(project);
@Test
public void fail_if_component_does_not_exist() {
expectedException.expect(NotFoundException.class);
expectedException.expectMessage("Component key 'does_not_exist' not found");

tester.newRequest().setParam("key", "does_not_exist").execute();
} }


private void addBrowsePermissionOnComponent(ComponentDto project) {
private void addPermissionTo(ComponentDto project) {
userSessionRule.addProjectPermission(UserRole.USER, project); userSessionRule.addProjectPermission(UserRole.USER, project);
} }


private ServerIssue call(String componentKey) {
try {
TestResponse response = tester.newRequest().setParam("key", componentKey).execute();
return ServerIssue.parseDelimitedFrom(response.getInputStream());
} catch (IOException e) {
throw Throwables.propagate(e);
}
private ServerIssue call(String componentKey) throws IOException {
TestResponse response = tester.newRequest().setParam("key", componentKey).execute();
return ServerIssue.parseDelimitedFrom(response.getInputStream());
}

private CloseableIterator<ServerIssue> callStream(String componentKey) {
TestResponse response = tester.newRequest().setParam("key", componentKey).execute();
return Protobuf.readStream(response.getInputStream(), ServerIssue.parser());
} }
} }

+ 16
- 112
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java View File

package org.sonar.server.issue.index; package org.sonar.server.issue.index;


import com.google.common.collect.Iterators; import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.sonar.api.config.internal.MapSettings; import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.issue.Issue; import org.sonar.api.issue.Issue;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity; import org.sonar.api.rule.Severity;
import org.sonar.api.utils.Duration; import org.sonar.api.utils.Duration;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.entry;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.api.utils.DateUtils.parseDate; import static org.sonar.api.utils.DateUtils.parseDate;
import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newFileDto;
assertThat( assertThat(
underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList(file.uuid())).build(), new SearchOptions()) underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList(file.uuid())).build(), new SearchOptions())
.getDocs()) .getDocs())
.isEmpty();
.isEmpty();
assertThat( assertThat(
underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList(module.uuid())).build(), new SearchOptions()) underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList(module.uuid())).build(), new SearchOptions())
.getDocs()) .getDocs())
.hasSize(1);
.hasSize(1);
assertThat( assertThat(
underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList(subModule.uuid())).build(), new SearchOptions()) underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList(subModule.uuid())).build(), new SearchOptions())
.getDocs()) .getDocs())
.hasSize(2);
.hasSize(2);
assertThat( assertThat(
underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList(project.uuid())).build(), new SearchOptions()) underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList(project.uuid())).build(), new SearchOptions())
.getDocs()) .getDocs())
.isEmpty();
.isEmpty();
assertThat( assertThat(
underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList("unknown")).build(), new SearchOptions()).getDocs()) underTest.search(IssueQuery.builder().projectUuids(newArrayList(project.uuid())).moduleUuids(newArrayList("unknown")).build(), new SearchOptions()).getDocs())
.isEmpty();
.isEmpty();
} }


@Test @Test
assertThat( assertThat(
underTest.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_FIXED)).build(), new SearchOptions()) underTest.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_FIXED)).build(), new SearchOptions())
.getDocs()) .getDocs())
.hasSize(2);
.hasSize(2);
assertThat(underTest.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE)).build(), new SearchOptions()).getDocs()).hasSize(1); assertThat(underTest.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE)).build(), new SearchOptions()).getDocs()).hasSize(1);
assertThat(underTest.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_REMOVED)).build(), new SearchOptions()).getDocs()).isEmpty(); assertThat(underTest.search(IssueQuery.builder().resolutions(newArrayList(Issue.RESOLUTION_REMOVED)).build(), new SearchOptions()).getDocs()).isEmpty();
} }
SearchOptions SearchOptions = fixtureForCreatedAtFacet(); SearchOptions SearchOptions = fixtureForCreatedAtFacet();


Map<String, Long> createdAt = underTest.search(IssueQuery.builder() Map<String, Long> createdAt = underTest.search(IssueQuery.builder()
.createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
.createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
.createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
.createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
SearchOptions).getFacets().get("createdAt"); SearchOptions).getFacets().get("createdAt");
assertThat(createdAt).containsOnly( assertThat(createdAt).containsOnly(
entry("2014-08-25T01:00:00+0000", 0L), entry("2014-08-25T01:00:00+0000", 0L),
SearchOptions SearchOptions = fixtureForCreatedAtFacet(); SearchOptions SearchOptions = fixtureForCreatedAtFacet();


Map<String, Long> createdAt = underTest.search(IssueQuery.builder() Map<String, Long> createdAt = underTest.search(IssueQuery.builder()
.createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
.createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
.createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
.createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
SearchOptions).getFacets().get("createdAt"); SearchOptions).getFacets().get("createdAt");
assertThat(createdAt).containsOnly( assertThat(createdAt).containsOnly(
entry("2014-08-01T01:00:00+0000", 0L), entry("2014-08-01T01:00:00+0000", 0L),
SearchOptions SearchOptions = fixtureForCreatedAtFacet(); SearchOptions SearchOptions = fixtureForCreatedAtFacet();


Map<String, Long> createdAt = underTest.search(IssueQuery.builder() Map<String, Long> createdAt = underTest.search(IssueQuery.builder()
.createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
.createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
.createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
.createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
SearchOptions).getFacets().get("createdAt"); SearchOptions).getFacets().get("createdAt");
assertThat(createdAt).containsOnly( assertThat(createdAt).containsOnly(
entry("2010-01-01T01:00:00+0000", 0L), entry("2010-01-01T01:00:00+0000", 0L),
SearchOptions SearchOptions = fixtureForCreatedAtFacet(); SearchOptions SearchOptions = fixtureForCreatedAtFacet();


Map<String, Long> createdAt = underTest.search(IssueQuery.builder() Map<String, Long> createdAt = underTest.search(IssueQuery.builder()
.createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
.createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
.createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
.createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
SearchOptions).getFacets().get("createdAt"); SearchOptions).getFacets().get("createdAt");
assertThat(createdAt).containsOnly( assertThat(createdAt).containsOnly(
entry("2014-09-01T01:00:00+0000", 2L)); entry("2014-09-01T01:00:00+0000", 2L));
SearchOptions SearchOptions = fixtureForCreatedAtFacet(); SearchOptions SearchOptions = fixtureForCreatedAtFacet();


Map<String, Long> createdAt = underTest.search(IssueQuery.builder() Map<String, Long> createdAt = underTest.search(IssueQuery.builder()
.createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
.createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
SearchOptions).getFacets().get("createdAt"); SearchOptions).getFacets().get("createdAt");
assertThat(createdAt).containsOnly( assertThat(createdAt).containsOnly(
entry("2011-01-01T01:00:00+0000", 1L), entry("2011-01-01T01:00:00+0000", 1L),
String key = "ISSUE" + i; String key = "ISSUE" + i;
issues.add(newDoc(key, file)); issues.add(newDoc(key, file));
} }
indexIssues(issues.toArray(new IssueDoc[] {}));
indexIssues(issues.toArray(new IssueDoc[]{}));


IssueQuery.Builder query = IssueQuery.builder(); IssueQuery.Builder query = IssueQuery.builder();
SearchResult<IssueDoc> result = underTest.search(query.build(), new SearchOptions().setLimit(Integer.MAX_VALUE)); SearchResult<IssueDoc> result = underTest.search(query.build(), new SearchOptions().setLimit(Integer.MAX_VALUE));
assertThat(underTest.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(1); assertThat(underTest.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(1);
} }


@Test
public void search_issues_for_batch_return_needed_fields() {
ComponentDto project = newPrivateProjectDto(newOrganizationDto(), "PROJECT");
ComponentDto file = newFileDto(project, null).setPath("src/File.xoo");

IssueDoc issue = newDoc("ISSUE", file)
.setRuleKey("squid:S001")
.setChecksum("12345")
.setAssignee("john")
.setLine(11)
.setMessage("the message")
.setSeverity(Severity.BLOCKER)
.setManualSeverity(true)
.setStatus(Issue.STATUS_RESOLVED)
.setResolution(Issue.RESOLUTION_FIXED)
.setType(BUG)
.setFuncCreationDate(new Date());
indexIssues(issue);

List<IssueDoc> issues = Lists.newArrayList(underTest.selectIssuesForBatch(file));
assertThat(issues).hasSize(1);
IssueDoc result = issues.get(0);
assertThat(result.key()).isEqualTo("ISSUE");
assertThat(result.moduleUuid()).isEqualTo("PROJECT");
assertThat(result.filePath()).isEqualTo("src/File.xoo");
assertThat(result.ruleKey()).isEqualTo(RuleKey.of("squid", "S001"));
assertThat(result.checksum()).isEqualTo("12345");
assertThat(result.assignee()).isEqualTo("john");
assertThat(result.line()).isEqualTo(11);
assertThat(result.message()).isEqualTo("the message");
assertThat(result.severity()).isEqualTo(Severity.BLOCKER);
assertThat(result.isManualSeverity()).isTrue();
assertThat(result.status()).isEqualTo(Issue.STATUS_RESOLVED);
assertThat(result.resolution()).isEqualTo(Issue.RESOLUTION_FIXED);
assertThat(result.type()).isEqualTo(BUG);
assertThat(result.creationDate()).isNotNull();
}

@Test
public void search_issues_for_batch() {
ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
ComponentDto module = ComponentTesting.newModuleDto(project);
ComponentDto subModule = ComponentTesting.newModuleDto(module);
ComponentDto file = newFileDto(subModule, null);

indexIssues(
newDoc("ISSUE3", module),
newDoc("ISSUE5", subModule),
newDoc("ISSUE2", file),
// Close Issue, should never be returned
newDoc("CLOSE_ISSUE", file).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED));

assertThat(Lists.newArrayList(underTest.selectIssuesForBatch(project))).hasSize(3);
assertThat(Lists.newArrayList(underTest.selectIssuesForBatch(module))).hasSize(3);
assertThat(Lists.newArrayList(underTest.selectIssuesForBatch(subModule))).hasSize(2);
assertThat(Lists.newArrayList(underTest.selectIssuesForBatch(file))).hasSize(1);
assertThat(Lists.newArrayList(underTest.selectIssuesForBatch(ComponentTesting.newPrivateProjectDto(newOrganizationDto())))).isEmpty();
}

@Test
public void fail_to_search_issues_for_batch_on_not_allowed_scope() {
try {
underTest.selectIssuesForBatch(new ComponentDto().setScope(Scopes.DIRECTORY));
failBecauseExceptionWasNotThrown(IllegalStateException.class);
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Component of scope 'DIR' is not allowed");
}
}

@Test
public void search_issues_for_batch_return_only_authorized_issues() {
OrganizationDto org = newOrganizationDto();
ComponentDto project1 = ComponentTesting.newPrivateProjectDto(org);
ComponentDto project2 = ComponentTesting.newPrivateProjectDto(org);
ComponentDto file1 = newFileDto(project1, null);
ComponentDto file2 = newFileDto(project2, null);
GroupDto allowedGroup = newGroupDto();
GroupDto otherGroup = newGroupDto();

// project1 can be seen by allowedGroup
indexIssue(newDoc("ISSUE1", file1));
authorizationIndexerTester.allowOnlyGroup(project1, allowedGroup);
// project3 can be seen by nobody
indexIssue(newDoc("ISSUE3", file2));

userSessionRule.logIn().setGroups(allowedGroup);
assertThat(Lists.newArrayList(underTest.selectIssuesForBatch(project1))).hasSize(1);

userSessionRule.logIn().setGroups(otherGroup);
assertThat(Lists.newArrayList(underTest.selectIssuesForBatch(project2))).isEmpty();
}

@Test @Test
public void list_tags() { public void list_tags() {
RuleDefinitionDto r1 = dbTester.rules().insert(); RuleDefinitionDto r1 = dbTester.rules().insert();

Loading…
Cancel
Save