@@ -19,10 +19,8 @@ | |||
*/ | |||
package org.sonar.server.computation.issue; | |||
import com.google.common.collect.ImmutableMap; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import org.apache.ibatis.session.ResultContext; | |||
import org.apache.ibatis.session.ResultHandler; | |||
@@ -60,8 +58,7 @@ public class BaseIssuesLoader { | |||
DbSession session = dbClient.openSession(false); | |||
final List<DefaultIssue> result = new ArrayList<>(); | |||
try { | |||
Map<String, String> params = ImmutableMap.of("componentUuid", componentUuid); | |||
session.select(IssueMapper.class.getName() + ".selectNonClosedByComponentUuid", params, new ResultHandler() { | |||
session.getMapper(IssueMapper.class).selectNonClosedByComponentUuid(componentUuid, new ResultHandler() { | |||
@Override | |||
public void handleResult(ResultContext resultContext) { | |||
DefaultIssue issue = ((IssueDto) resultContext.getResultObject()).toDefaultIssue(); |
@@ -20,7 +20,6 @@ | |||
package org.sonar.server.computation.issue; | |||
import com.google.common.collect.Lists; | |||
import com.google.common.collect.Sets; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.List; | |||
@@ -34,6 +33,8 @@ import org.sonar.core.issue.tracking.Input; | |||
import org.sonar.core.issue.tracking.LazyInput; | |||
import org.sonar.core.issue.tracking.LineHashSequence; | |||
import org.sonar.core.util.CloseableIterator; | |||
import org.sonar.db.protobuf.DbCommons; | |||
import org.sonar.db.protobuf.DbIssues; | |||
import org.sonar.server.computation.batch.BatchReportReader; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.computation.component.TreeRootHolder; | |||
@@ -127,6 +128,22 @@ public class TrackerRawInputFactory { | |||
if (reportIssue.hasAttributes()) { | |||
issue.setAttributes(KeyValueFormat.parse(reportIssue.getAttributes())); | |||
} | |||
DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder(); | |||
if (reportIssue.hasPrimaryLocation()) { | |||
BatchReport.IssueLocation location = reportIssue.getPrimaryLocation(); | |||
dbLocationsBuilder.setPrimary(convertLocation(location)); | |||
} | |||
for (BatchReport.IssueLocation location : reportIssue.getAdditionalLocationList()) { | |||
dbLocationsBuilder.addSecondary(convertLocation(location)); | |||
} | |||
for (BatchReport.ExecutionFlow flow : reportIssue.getExecutionFlowList()) { | |||
DbIssues.ExecutionFlow.Builder dbFlowBuilder = DbIssues.ExecutionFlow.newBuilder(); | |||
for (BatchReport.IssueLocation location : flow.getLocationList()) { | |||
dbFlowBuilder.addLocations(convertLocation(location)); | |||
} | |||
dbLocationsBuilder.addExecutionFlow(dbFlowBuilder); | |||
} | |||
issue.setLocations(dbLocationsBuilder.build()); | |||
return issue; | |||
} | |||
@@ -139,5 +156,33 @@ public class TrackerRawInputFactory { | |||
issue.setProjectKey(treeRootHolder.getRoot().getKey()); | |||
return issue; | |||
} | |||
private DbIssues.Location convertLocation(BatchReport.IssueLocation source) { | |||
DbIssues.Location.Builder target = DbIssues.Location.newBuilder(); | |||
if (source.hasComponentRef() && source.getComponentRef() != component.getRef()) { | |||
target.setComponentId(treeRootHolder.getComponentByRef(source.getComponentRef()).getUuid()); | |||
} | |||
if (source.hasMsg()) { | |||
target.setMsg(source.getMsg()); | |||
} | |||
if (source.hasTextRange()) { | |||
BatchReport.TextRange sourceRange = source.getTextRange(); | |||
DbCommons.TextRange.Builder targetRange = DbCommons.TextRange.newBuilder(); | |||
if (sourceRange.hasStartLine()) { | |||
targetRange.setStartLine(sourceRange.getStartLine()); | |||
} | |||
if (sourceRange.hasStartOffset()) { | |||
targetRange.setStartOffset(sourceRange.getStartOffset()); | |||
} | |||
if (sourceRange.hasEndLine()) { | |||
targetRange.setEndLine(sourceRange.getEndLine()); | |||
} | |||
if (sourceRange.hasEndOffset()) { | |||
targetRange.setEndOffset(sourceRange.getEndOffset()); | |||
} | |||
target.setTextRange(targetRange); | |||
} | |||
return target.build(); | |||
} | |||
} | |||
} |
@@ -40,6 +40,8 @@ import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.ActionPlanDto; | |||
import org.sonar.db.issue.IssueChangeDto; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.protobuf.DbCommons; | |||
import org.sonar.db.protobuf.DbIssues; | |||
import org.sonar.db.rule.RuleDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.markdown.Markdown; | |||
@@ -165,6 +167,7 @@ public class SearchResponseFormat { | |||
issueBuilder.setStatus(nullToEmpty(dto.getStatus())); | |||
issueBuilder.setActionPlan(nullToEmpty(dto.getActionPlanKey())); | |||
issueBuilder.setMessage(nullToEmpty(dto.getMessage())); | |||
issueBuilder.setTagsPresentIfEmpty(true); | |||
issueBuilder.addAllTags(dto.getTags()); | |||
Long debt = dto.getDebt(); | |||
if (debt != null) { | |||
@@ -174,6 +177,7 @@ public class SearchResponseFormat { | |||
if (line != null) { | |||
issueBuilder.setLine(line); | |||
} | |||
completeIssueLocations(dto, issueBuilder); | |||
issueBuilder.setAuthor(nullToEmpty(dto.getAuthorLogin())); | |||
Date date = dto.getIssueCreationDate(); | |||
if (date != null) { | |||
@@ -189,7 +193,55 @@ public class SearchResponseFormat { | |||
} | |||
} | |||
private void formatIssueTransitions(SearchResponseData data, Issues.Issue.Builder issueBuilder, IssueDto dto) { | |||
private void completeIssueLocations(IssueDto dto, Issues.Issue.Builder issueBuilder) { | |||
DbIssues.Locations locations = dto.parseLocations(); | |||
if (locations != null) { | |||
if (locations.hasPrimary()) { | |||
DbIssues.Location primary = locations.getPrimary(); | |||
issueBuilder.setLocation(convertLocation(primary)); | |||
} | |||
for (DbIssues.Location secondary : locations.getSecondaryList()) { | |||
issueBuilder.addSecondaryLocations(convertLocation(secondary)); | |||
} | |||
for (DbIssues.ExecutionFlow flow : locations.getExecutionFlowList()) { | |||
Issues.ExecutionFlow.Builder targetFlow = Issues.ExecutionFlow.newBuilder(); | |||
for (DbIssues.Location flowLocation : flow.getLocationsList()) { | |||
targetFlow.addLocations(convertLocation(flowLocation)); | |||
} | |||
issueBuilder.addExecutionFlows(targetFlow); | |||
} | |||
} | |||
} | |||
private static Issues.Location convertLocation(DbIssues.Location source) { | |||
Issues.Location.Builder target = Issues.Location.newBuilder(); | |||
if (source.hasComponentId()) { | |||
target.setComponentId(source.getComponentId()); | |||
} | |||
if (source.hasMsg()) { | |||
target.setMsg(source.getMsg()); | |||
} | |||
if (source.hasTextRange()) { | |||
DbCommons.TextRange sourceRange = source.getTextRange(); | |||
Common.TextRange.Builder targetRange = Common.TextRange.newBuilder(); | |||
if (sourceRange.hasStartLine()) { | |||
targetRange.setStartLine(sourceRange.getStartLine()); | |||
} | |||
if (sourceRange.hasStartOffset()) { | |||
targetRange.setStartOffset(sourceRange.getStartOffset()); | |||
} | |||
if (sourceRange.hasEndLine()) { | |||
targetRange.setEndLine(sourceRange.getEndLine()); | |||
} | |||
if (sourceRange.hasEndOffset()) { | |||
targetRange.setEndOffset(sourceRange.getEndOffset()); | |||
} | |||
target.setTextRange(targetRange); | |||
} | |||
return target.build(); | |||
} | |||
private static void formatIssueTransitions(SearchResponseData data, Issues.Issue.Builder issueBuilder, IssueDto dto) { | |||
issueBuilder.setTransitionsPresentIfEmpty(true); | |||
List<Transition> transitions = data.getTransitionsForIssueKey(dto.getKey()); | |||
if (transitions != null) { | |||
@@ -199,7 +251,7 @@ public class SearchResponseFormat { | |||
} | |||
} | |||
private void formatIssueActions(SearchResponseData data, Issues.Issue.Builder issueBuilder, IssueDto dto) { | |||
private static void formatIssueActions(SearchResponseData data, Issues.Issue.Builder issueBuilder, IssueDto dto) { | |||
issueBuilder.setActionsPresentIfEmpty(true); | |||
List<String> actions = data.getActionsForIssueKey(dto.getKey()); | |||
if (actions != null) { | |||
@@ -207,7 +259,7 @@ public class SearchResponseFormat { | |||
} | |||
} | |||
private void formatIssueComments(SearchResponseData data, Issues.Issue.Builder issueBuilder, IssueDto dto) { | |||
private static void formatIssueComments(SearchResponseData data, Issues.Issue.Builder issueBuilder, IssueDto dto) { | |||
issueBuilder.setCommentsPresentIfEmpty(true); | |||
List<IssueChangeDto> comments = data.getCommentsForIssueKey(dto.getKey()); | |||
if (comments != null) { | |||
@@ -241,7 +293,7 @@ public class SearchResponseFormat { | |||
return result; | |||
} | |||
private List<Issues.Component> formatComponents(SearchResponseData data) { | |||
private static List<Issues.Component> formatComponents(SearchResponseData data) { | |||
List<Issues.Component> result = new ArrayList<>(); | |||
Collection<ComponentDto> components = data.getComponents(); | |||
if (components != null) { |
@@ -35,6 +35,7 @@ import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.IssueChangeDto; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.protobuf.DbIssues; | |||
import org.sonar.server.es.Facets; | |||
import org.sonar.server.issue.ActionService; | |||
import org.sonar.server.issue.IssueCommentService; | |||
@@ -181,6 +182,28 @@ public class SearchResponseLoader { | |||
add(RULES, issue.getRuleKey()); | |||
add(USERS, issue.getReporter()); | |||
add(USERS, issue.getAssignee()); | |||
collectIssueLocations(issue); | |||
} | |||
} | |||
private void collectIssueLocations(IssueDto issue) { | |||
DbIssues.Locations locations = issue.parseLocations(); | |||
if (locations != null) { | |||
if (locations.hasPrimary() && locations.getPrimary().hasComponentId()) { | |||
componentUuids.add(locations.getPrimary().getComponentId()); | |||
} | |||
for (DbIssues.Location location : locations.getSecondaryList()) { | |||
if (location.hasComponentId()) { | |||
componentUuids.add(location.getComponentId()); | |||
} | |||
} | |||
for (DbIssues.ExecutionFlow flow : locations.getExecutionFlowList()) { | |||
for (DbIssues.Location location : flow.getLocationsList()) { | |||
if (location.hasComponentId()) { | |||
componentUuids.add(location.getComponentId()); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
@@ -190,10 +213,6 @@ public class SearchResponseLoader { | |||
} | |||
} | |||
public void addComponentUuid(String uuid) { | |||
this.componentUuids.add(uuid); | |||
} | |||
public void addComponentUuids(@Nullable Collection<String> uuids) { | |||
if (uuids != null) { | |||
this.componentUuids.addAll(uuids); |
@@ -103,6 +103,7 @@ public final class IssueDto implements Serializable { | |||
return new IssueDto() | |||
.setKee(issue.key()) | |||
.setLine(issue.line()) | |||
.setLocations((DbIssues.Locations) issue.getLocations()) | |||
.setMessage(issue.message()) | |||
.setEffortToFix(issue.effortToFix()) | |||
.setDebt(issue.debtInMinutes()) | |||
@@ -150,6 +151,7 @@ public final class IssueDto implements Serializable { | |||
return new IssueDto() | |||
.setKee(issue.key()) | |||
.setLine(issue.line()) | |||
.setLocations((DbIssues.Locations) issue.getLocations()) | |||
.setMessage(issue.message()) | |||
.setEffortToFix(issue.effortToFix()) | |||
.setDebt(issue.debtInMinutes()) | |||
@@ -665,7 +667,7 @@ public final class IssueDto implements Serializable { | |||
return null; | |||
} | |||
public IssueDto setLocations(byte[] locations) { | |||
public IssueDto setLocations(@Nullable byte[] locations) { | |||
this.locations = locations; | |||
return this; | |||
} | |||
@@ -715,6 +717,7 @@ public final class IssueDto implements Serializable { | |||
issue.setCloseDate(longToDate(issueCloseDate)); | |||
issue.setUpdateDate(longToDate(issueUpdateDate)); | |||
issue.setSelectedAt(selectedAt); | |||
issue.setLocations(parseLocations()); | |||
return issue; | |||
} | |||
} |
@@ -21,12 +21,14 @@ package org.sonar.db.issue; | |||
import java.util.List; | |||
import java.util.Set; | |||
import org.apache.ibatis.annotations.Param; | |||
import org.apache.ibatis.session.ResultHandler; | |||
public interface IssueMapper { | |||
IssueDto selectByKey(String key); | |||
List<IssueDto> selectNonClosedByComponentUuid(String componentUuid); | |||
void selectNonClosedByComponentUuid(@Param("componentUuid") String componentUuid, ResultHandler resultHandler); | |||
Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(String projectUuid); | |||
@@ -32,8 +32,8 @@ option optimize_for = SPEED; | |||
message Locations { | |||
optional Location primary = 1; | |||
repeated Location secondaries = 2; | |||
repeated ExecutionFlow execution_flows = 3; | |||
repeated Location secondary = 2; | |||
repeated ExecutionFlow execution_flow = 3; | |||
} | |||
message ExecutionFlow { |
@@ -13,6 +13,7 @@ | |||
i.manual_severity as manualSeverity, | |||
i.message as message, | |||
i.line as line, | |||
i.locations as locations, | |||
i.effort_to_fix as effortToFix, | |||
i.technical_debt as debt, | |||
i.status as status, | |||
@@ -67,12 +68,13 @@ | |||
<insert id="insert" parameterType="Issue" useGeneratedKeys="false" keyProperty="id"> | |||
INSERT INTO issues (kee, rule_id, action_plan_key, severity, manual_severity, | |||
message, line, effort_to_fix, technical_debt, status, tags, | |||
message, line, locations, effort_to_fix, technical_debt, status, tags, | |||
resolution, checksum, reporter, assignee, author_login, issue_attributes, issue_creation_date, issue_update_date, | |||
issue_close_date, created_at, updated_at, component_uuid, project_uuid) | |||
VALUES (#{kee,jdbcType=VARCHAR}, #{ruleId,jdbcType=INTEGER}, #{actionPlanKey,jdbcType=VARCHAR}, | |||
#{severity,jdbcType=VARCHAR}, | |||
#{manualSeverity,jdbcType=BOOLEAN}, #{message,jdbcType=VARCHAR}, #{line,jdbcType=INTEGER}, | |||
#{locations,jdbcType=BLOB}, | |||
#{effortToFix,jdbcType=DOUBLE}, #{debt,jdbcType=INTEGER}, #{status,jdbcType=VARCHAR}, | |||
#{tagsString,jdbcType=VARCHAR}, #{resolution,jdbcType=VARCHAR}, #{checksum,jdbcType=VARCHAR}, | |||
#{reporter,jdbcType=VARCHAR}, #{assignee,jdbcType=VARCHAR}, #{authorLogin,jdbcType=VARCHAR}, | |||
@@ -92,6 +94,7 @@ | |||
manual_severity=#{manualSeverity,jdbcType=BOOLEAN}, | |||
message=#{message,jdbcType=VARCHAR}, | |||
line=#{line,jdbcType=INTEGER}, | |||
locations=#{locations,jdbcType=BLOB}, | |||
effort_to_fix=#{effortToFix,jdbcType=DOUBLE}, | |||
technical_debt=#{debt,jdbcType=INTEGER}, | |||
status=#{status,jdbcType=VARCHAR}, | |||
@@ -120,6 +123,7 @@ | |||
manual_severity=#{manualSeverity,jdbcType=BOOLEAN}, | |||
message=#{message,jdbcType=VARCHAR}, | |||
line=#{line,jdbcType=INTEGER}, | |||
locations=#{locations,jdbcType=BLOB}, | |||
effort_to_fix=#{effortToFix,jdbcType=DOUBLE}, | |||
technical_debt=#{debt,jdbcType=INTEGER}, | |||
status=#{status,jdbcType=VARCHAR}, | |||
@@ -172,6 +176,7 @@ | |||
i.manual_severity as manualSeverity, | |||
i.message as message, | |||
i.line as line, | |||
i.locations as locations, | |||
i.effort_to_fix as effortToFix, | |||
i.technical_debt as debt, | |||
i.status as status, |
@@ -29,9 +29,9 @@ import org.junit.rules.ExpectedException; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.RowNotFoundException; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.rule.RuleTesting; | |||
import org.sonar.db.RowNotFoundException; | |||
import org.sonar.test.DbTests; | |||
import static java.util.Arrays.asList; | |||
@@ -126,6 +126,8 @@ public class IssueDaoTest { | |||
assertThat(issue.getRule()).isEqualTo("AvoidCycle"); | |||
assertThat(issue.getComponentKey()).isEqualTo("Action.java"); | |||
assertThat(issue.getProjectKey()).isEqualTo("struts"); | |||
assertThat(issue.getLocations()).isNull(); | |||
assertThat(issue.parseLocations()).isNull(); | |||
} | |||
@Test |
@@ -18,7 +18,7 @@ | |||
syntax = "proto2"; | |||
package sonarqube.ws; | |||
package sonarqube.ws.commons; | |||
option java_package = "org.sonarqube.ws"; | |||
option java_outer_classname = "Common"; | |||
@@ -71,3 +71,18 @@ message User { | |||
optional string email = 3; | |||
optional bool active = 4; | |||
} | |||
// Lines start at 1 and line offsets start at 0 | |||
message TextRange { | |||
// Start line. Should never be absent | |||
optional int32 start_line = 1; | |||
// End line (inclusive). Absent means it is same as start line | |||
optional int32 end_line = 2; | |||
// If absent it means range starts at the first offset of start line | |||
optional int32 start_offset = 3; | |||
// If absent it means range ends at the last offset of end line | |||
optional int32 end_offset = 4; | |||
} |
@@ -20,7 +20,7 @@ syntax = "proto2"; | |||
package sonarqube.ws.issues; | |||
import "ws-common.proto"; | |||
import "ws-commons.proto"; | |||
option java_package = "org.sonarqube.ws"; | |||
option java_outer_classname = "Issues"; | |||
@@ -31,7 +31,7 @@ message Search { | |||
optional int64 total = 1; | |||
optional int64 p = 2; | |||
optional int32 ps = 3; | |||
optional Paging paging = 4; | |||
optional sonarqube.ws.commons.Paging paging = 4; | |||
// Total amount of debt, only when the facet "total" is enabled | |||
optional int64 debtTotal = 5; | |||
@@ -39,23 +39,23 @@ message Search { | |||
repeated Issue issues = 6; | |||
repeated Component components = 7; | |||
optional bool rulesPresentIfEmpty = 8; | |||
repeated Rule rules = 9; | |||
repeated sonarqube.ws.commons.Rule rules = 9; | |||
optional bool usersPresentIfEmpty = 10; | |||
repeated User users = 11; | |||
repeated sonarqube.ws.commons.User users = 11; | |||
optional bool actionPlansPresentIfEmpty = 12; | |||
repeated ActionPlan actionPlans = 13; | |||
optional bool languagesPresentIfEmpty = 14; | |||
repeated Language languages = 15; | |||
optional bool facetsPresentIfEmpty = 16; | |||
repeated Facet facets = 17; | |||
repeated sonarqube.ws.commons.Facet facets = 17; | |||
} | |||
// Response of most of POST/issues/{operation}, for instance assign, add_comment and set_severity | |||
message Operation { | |||
optional Issue issue = 1; | |||
repeated Component components = 2; | |||
repeated Rule rules = 3; | |||
repeated User users = 4; | |||
repeated sonarqube.ws.commons.Rule rules = 3; | |||
repeated sonarqube.ws.commons.User users = 4; | |||
repeated ActionPlan actionPlans = 5; | |||
} | |||
@@ -63,41 +63,54 @@ message Operation { | |||
message Issue { | |||
optional string key = 1; | |||
optional string rule = 2; | |||
optional Severity severity = 3; | |||
optional sonarqube.ws.commons.Severity severity = 3; | |||
optional string component = 4; | |||
optional int64 componentId = 5; | |||
optional string project = 6; | |||
optional string subProject = 7; | |||
optional int32 line = 8; | |||
optional string resolution = 9; | |||
optional string status = 10; | |||
optional string message = 11; | |||
optional string debt = 12; | |||
optional string assignee = 13; | |||
optional string reporter = 14; | |||
optional Location location = 9; | |||
repeated Location secondaryLocations = 10; | |||
repeated ExecutionFlow executionFlows = 11; | |||
optional string resolution = 12; | |||
optional string status = 13; | |||
optional string message = 14; | |||
optional string debt = 15; | |||
optional string assignee = 16; | |||
optional string reporter = 17; | |||
// SCM login of the committer who introduced the issue | |||
optional string author = 15; | |||
optional string author = 18; | |||
optional string actionPlan = 16; | |||
optional string actionPlanName = 17; | |||
optional string attr = 18; | |||
repeated string tags = 19; | |||
optional string actionPlan = 19; | |||
optional bool tagsPresentIfEmpty = 20; | |||
repeated string tags = 21; | |||
// the transitions allowed for the requesting user. | |||
optional bool transitionsPresentIfEmpty = 20; | |||
repeated string transitions = 21; | |||
optional bool transitionsPresentIfEmpty = 22; | |||
repeated string transitions = 23; | |||
// the actions allowed for the requesting user. | |||
optional bool actionsPresentIfEmpty = 22; | |||
repeated string actions = 23; | |||
optional bool commentsPresentIfEmpty = 24; | |||
repeated Comment comments = 25; | |||
optional string creationDate = 26; | |||
optional string updateDate = 27; | |||
optional string fUpdateAge = 28; | |||
optional string closeDate = 29; | |||
optional bool actionsPresentIfEmpty = 24; | |||
repeated string actions = 25; | |||
optional bool commentsPresentIfEmpty = 26; | |||
repeated Comment comments = 27; | |||
optional string creationDate = 28; | |||
optional string updateDate = 29; | |||
optional string fUpdateAge = 30; | |||
optional string closeDate = 31; | |||
} | |||
message ExecutionFlow { | |||
repeated Location locations = 1; | |||
} | |||
message Location { | |||
optional string component_id = 1; | |||
// Only when component is a file. Can be empty for a file if this is an issue global to the file. | |||
optional sonarqube.ws.commons.TextRange text_range = 2; | |||
optional string msg = 3; | |||
} | |||
message Comment { |