Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ public record IssueDTO (
String pullRequestId,
String status, // open|resolved|ignored
OffsetDateTime createdAt,
String issueCategory
String issueCategory,
// Detection info - where was this issue first found
Long analysisId,
Long prNumber,
String commitHash,
OffsetDateTime detectedAt
) {
public static IssueDTO fromEntity(CodeAnalysisIssue issue) {
String categoryStr = issue.getIssueCategory() != null
? issue.getIssueCategory().name()
: IssueCategory.CODE_QUALITY.name();

var analysis = issue.getAnalysis();
return new IssueDTO(
String.valueOf(issue.getId()),
categoryStr,
Expand All @@ -37,11 +44,16 @@ public static IssueDTO fromEntity(CodeAnalysisIssue issue) {
issue.getLineNumber(),
null,
null,
issue.getAnalysis() == null ? null : issue.getAnalysis().getBranchName(),
issue.getAnalysis() == null || issue.getAnalysis().getPrNumber() == null ? null : String.valueOf(issue.getAnalysis().getPrNumber()),
analysis == null ? null : analysis.getBranchName(),
analysis == null || analysis.getPrNumber() == null ? null : String.valueOf(analysis.getPrNumber()),
issue.isResolved() ? "resolved" : "open",
issue.getCreatedAt(),
categoryStr
categoryStr,
// Detection info
analysis != null ? analysis.getId() : null,
analysis != null ? analysis.getPrNumber() : null,
analysis != null ? analysis.getCommitHash() : null,
issue.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,12 @@ Page<BranchIssue> findAllByBranchIdPaged(
@Modifying
@Query("DELETE FROM BranchIssue bi WHERE bi.branch.id IN (SELECT b.id FROM Branch b WHERE b.project.id = :projectId)")
void deleteByProjectId(@Param("projectId") Long projectId);

// Base query for filtered branch issues - filtering is done in service layer
@Query(value = "SELECT bi FROM BranchIssue bi " +
"JOIN FETCH bi.codeAnalysisIssue cai " +
"WHERE bi.branch.id = :branchId " +
"ORDER BY cai.id DESC",
countQuery = "SELECT COUNT(bi) FROM BranchIssue bi WHERE bi.branch.id = :branchId")
List<BranchIssue> findAllByBranchIdWithIssues(@Param("branchId") Long branchId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ public int getMaxAnalysisPrVersion(Long projectId, Long prNumber) {
return codeAnalysisRepository.findMaxPrVersion(projectId, prNumber).orElse(0);
}

public Optional<CodeAnalysis> findAnalysisByProjectAndPrNumberAndVersion(Long projectId, Long prNumber, int prVersion) {
return codeAnalysisRepository.findByProjectIdAndPrNumberAndPrVersion(projectId, prNumber, prVersion);
}

private CodeAnalysisIssue createIssueFromData(Map<String, Object> issueData, String issueKey) {
try {
CodeAnalysisIssue issue = new CodeAnalysisIssue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ public ResponseEntity<AnalysisIssueResponse> listIssues(
if(pullRequestId != null) {
maxVersion = codeAnalysisService.getMaxAnalysisPrVersion(project.getId(), Long.parseLong(pullRequestId));
resp.setMaxVersion(maxVersion);

// Fetch the analysis comment/summary for the specific version
int versionToFetch = prVersion > 0 ? prVersion : maxVersion;
var analysisOpt = codeAnalysisService.findAnalysisByProjectAndPrNumberAndVersion(
project.getId(),
Long.parseLong(pullRequestId),
versionToFetch
);
analysisOpt.ifPresent(analysis -> resp.setAnalysisSummary(analysis.getComment()));
}
List<CodeAnalysisIssue> issues = analysisService.findIssues(project.getId(), branch, pullRequestId, severity, type, (prVersion > 0 ? prVersion : maxVersion));
List<IssueDTO> issueDTOs = issues.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ public ResponseEntity<Map<String, Object>> listBranchIssues(
@PathVariable String projectNamespace,
@PathVariable String branchName,
@RequestParam(required = false, defaultValue = "open") String status,
@RequestParam(required = false) String severity,
@RequestParam(required = false) String category,
@RequestParam(required = false) String filePath,
@RequestParam(required = false) String dateFrom,
@RequestParam(required = false) String dateTo,
@RequestParam(required = false, defaultValue = "1") int page,
@RequestParam(required = false, defaultValue = "50") int pageSize
) {
Expand All @@ -114,26 +119,96 @@ public ResponseEntity<Map<String, Object>> listBranchIssues(
}
Branch branch = branchOpt.get();

// Use paginated queries from repository
org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(page - 1, pageSize);
org.springframework.data.domain.Page<BranchIssue> branchIssuePage;
long total;
// Normalize filter values
String normalizedStatus = (status == null || status.isBlank()) ? "open" : status.toLowerCase();
String normalizedSeverity = (severity == null || severity.isBlank() || "ALL".equalsIgnoreCase(severity)) ? null : severity.toUpperCase();
String normalizedCategory = (category == null || category.isBlank() || "ALL".equalsIgnoreCase(category)) ? null : category.toUpperCase();
String normalizedFilePath = (filePath == null || filePath.isBlank()) ? null : filePath.toLowerCase();

if ("all".equalsIgnoreCase(status)) {
branchIssuePage = branchIssueRepository.findAllByBranchIdPaged(branch.getId(), pageable);
total = branchIssueRepository.countAllByBranchId(branch.getId());
} else if ("resolved".equalsIgnoreCase(status)) {
branchIssuePage = branchIssueRepository.findResolvedByBranchIdPaged(branch.getId(), pageable);
total = branchIssueRepository.countResolvedByBranchId(branch.getId());
} else {
// Default to "open" (unresolved)
branchIssuePage = branchIssueRepository.findUnresolvedByBranchIdPaged(branch.getId(), pageable);
total = branchIssueRepository.countUnresolvedByBranchId(branch.getId());
// Parse date filters
java.time.OffsetDateTime parsedDateFrom = null;
java.time.OffsetDateTime parsedDateTo = null;
if (dateFrom != null && !dateFrom.isBlank()) {
try {
parsedDateFrom = java.time.OffsetDateTime.parse(dateFrom);
} catch (Exception e) {
try {
parsedDateFrom = java.time.LocalDate.parse(dateFrom).atStartOfDay().atOffset(java.time.ZoneOffset.UTC);
} catch (Exception ignored) {}
}
}
if (dateTo != null && !dateTo.isBlank()) {
try {
parsedDateTo = java.time.OffsetDateTime.parse(dateTo);
} catch (Exception e) {
try {
parsedDateTo = java.time.LocalDate.parse(dateTo).atTime(23, 59, 59).atOffset(java.time.ZoneOffset.UTC);
} catch (Exception ignored) {}
}
}

//TODO: use SQL instead....
// Fetch all issues for the branch and filter in Java (avoids complex SQL with nullable params)
List<BranchIssue> allBranchIssues = branchIssueRepository.findAllByBranchIdWithIssues(branch.getId());

// Apply filters in Java
final java.time.OffsetDateTime finalDateFrom = parsedDateFrom;
final java.time.OffsetDateTime finalDateTo = parsedDateTo;

List<IssueDTO> pagedIssues = branchIssuePage.getContent().stream()
List<BranchIssue> filteredIssues = allBranchIssues.stream()
.filter(bi -> {
// Status filter
if ("open".equals(normalizedStatus) && bi.isResolved()) return false;
if ("resolved".equals(normalizedStatus) && !bi.isResolved()) return false;
// "all" passes everything

var issue = bi.getCodeAnalysisIssue();
if (issue == null) return false;

// Severity filter
if (normalizedSeverity != null) {
if (issue.getSeverity() == null) return false;
if (!normalizedSeverity.equals(issue.getSeverity().name())) return false;
}

// Category filter
if (normalizedCategory != null) {
if (issue.getIssueCategory() == null) return false;
if (!normalizedCategory.equals(issue.getIssueCategory().name())) return false;
}

// File path filter (partial match, case insensitive)
if (normalizedFilePath != null) {
if (issue.getFilePath() == null) return false;
if (!issue.getFilePath().toLowerCase().contains(normalizedFilePath)) return false;
}

// Date from filter
if (finalDateFrom != null && issue.getCreatedAt() != null) {
if (issue.getCreatedAt().isBefore(finalDateFrom)) return false;
}

// Date to filter
if (finalDateTo != null && issue.getCreatedAt() != null) {
if (issue.getCreatedAt().isAfter(finalDateTo)) return false;
}

return true;
})
.sorted((a, b) -> Long.compare(b.getCodeAnalysisIssue().getId(), a.getCodeAnalysisIssue().getId()))
.toList();

long total = filteredIssues.size();

// Apply pagination
int startIndex = (page - 1) * pageSize;
int endIndex = Math.min(startIndex + pageSize, filteredIssues.size());

List<IssueDTO> pagedIssues = (startIndex < filteredIssues.size())
? filteredIssues.subList(startIndex, endIndex).stream()
.map(bi -> IssueDTO.fromEntity(bi.getCodeAnalysisIssue()))
.toList();
.toList()
: List.of();

Map<String, Object> response = new HashMap<>();
response.put("issues", pagedIssues);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class AnalysisIssueResponse {
private List<IssueDTO> issues = new ArrayList<>();
private IssuesSummaryDTO summary = new IssuesSummaryDTO(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
private int maxVersion;
private String analysisSummary; // The comment/summary from the CodeAnalysis

public AnalysisIssueResponse() {
}
Expand All @@ -34,4 +35,11 @@ public int getMaxVersion() {
public void setMaxVersion(int maxVersion) {
this.maxVersion = maxVersion;
}

public String getAnalysisSummary() {
return analysisSummary;
}
public void setAnalysisSummary(String analysisSummary) {
this.analysisSummary = analysisSummary;
}
}
3 changes: 2 additions & 1 deletion python-ecosystem/mcp-client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ target/
server.log
*.jar

logs/**
logs/**
**/__pycache__/
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 2 additions & 2 deletions python-ecosystem/mcp-client/utils/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def build_first_review_prompt(
- Do NOT include any "id" field in issues - it will be assigned by the system
- Each issue MUST have: severity, category, file, line, reason, isResolved
- REQUIRED FOR ALL ISSUES: Include "suggestedFixDescription" AND "suggestedFixDiff" with actual code fix in unified diff format
- The suggestedFixDiff must show the exact code change to fix the issue - this is MANDATORY, not optional
- The suggestedFixDiff must show the exact code change to fix the issue

If no issues are found, return:
{{
Expand Down Expand Up @@ -351,7 +351,7 @@ def build_review_prompt_with_previous_analysis_data(
- Do NOT include any "id" field in issues - it will be assigned by the system
- Each issue MUST have: severity, category, file, line, reason, isResolved
- REQUIRED FOR ALL ISSUES: Include "suggestedFixDescription" AND "suggestedFixDiff" with actual code fix in unified diff format
- The suggestedFixDiff must show the exact code change to fix the issue - this is MANDATORY, not optional
- The suggestedFixDiff must show the exact code change to fix the issue

If no issues are found, return:
{{
Expand Down
1 change: 1 addition & 0 deletions python-ecosystem/rag-pipeline/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ build/
.vscode/

logs/**
**/__pycache__/