Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create detection LLM endpoint #232

Merged
21 changes: 21 additions & 0 deletions server/application-server/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,27 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Message"
/activity/{pullRequestId}/badpractices/:
post:
tags:
- activity
operationId: detectBadPracticesForPullRequest
parameters:
- name: pullRequestId
in: path
required: true
schema:
type: integer
format: int64
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/PullRequestBadPractice"
/activity/{login}/badpractices:
post:
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ public ResponseEntity<ActivityDTO> getActivityByUser(@PathVariable String login)

@PostMapping("/{login}/badpractices")
public ResponseEntity<List<PullRequestBadPracticeDTO>> detectBadPracticesByUser(@PathVariable String login) {
List<PullRequestBadPracticeDTO> badPractices = activityService.detectBadPractices(login);
List<PullRequestBadPracticeDTO> badPractices = activityService.detectBadPracticesForUser(login);
return ResponseEntity.ok(badPractices);
}

@PostMapping("{pullRequestId}/badpractices/")
public ResponseEntity<List<PullRequestBadPracticeDTO>> detectBadPracticesForPullRequest(@PathVariable Long pullRequestId) {
List<PullRequestBadPracticeDTO> badPractice = activityService.detectBadPracticesForPullRequest(pullRequestId);
return ResponseEntity.ok(badPractice);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequestRepository;
import de.tum.in.www1.hephaestus.gitprovider.user.UserRepository;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -33,7 +34,6 @@ public class ActivityService {
@Autowired
private UserRepository userRepository;

@Transactional
public ActivityDTO getActivity(String login) {
logger.info("Getting activity for user with login: {}", login);

Expand Down Expand Up @@ -61,35 +61,40 @@ public ActivityDTO getActivity(String login) {
.map(pullRequest ->
PullRequestWithBadPracticesDTO.fromPullRequest(
pullRequest,
pullRequestBadPracticesMap.getOrDefault(
pullRequest,
List.of(
new PullRequestBadPracticeDTO("Unchecked checkbox.", "The checkbox is not checked.", false)
)
)
pullRequestBadPracticesMap.getOrDefault(pullRequest, List.of())
)
)
.collect(Collectors.toList());

return new ActivityDTO(openPullRequestsWithBadPractices);
}

@Transactional
public List<PullRequestBadPracticeDTO> detectBadPractices(String login) {
public List<PullRequestBadPracticeDTO> detectBadPracticesForUser(String login) {
logger.info("Detecting bad practices for user with login: {}", login);

userRepository.findByLogin(login).orElseThrow(() -> new IllegalArgumentException("User not found"));

List<PullRequest> pullRequests = pullRequestRepository.findAssignedByLoginAndStates(
login,
Set.of(Issue.State.OPEN)
);

return pullRequests
.stream()
.map(pullRequestBadPracticeDetector::detectAndSyncBadPractices)
.flatMap(List::stream)
.map(PullRequestBadPracticeDTO::fromPullRequestBadPractice)
.collect(Collectors.toList());
List<PullRequestBadPractice> detectedBadPractices = new ArrayList<>();
for (PullRequest pullRequest : pullRequests) {
detectedBadPractices.addAll(pullRequestBadPracticeDetector.detectAndSyncBadPractices(pullRequest));
}
return detectedBadPractices.stream().map(PullRequestBadPracticeDTO::fromPullRequestBadPractice).toList();
}

public List<PullRequestBadPracticeDTO> detectBadPracticesForPullRequest(Long pullRequestId) {
logger.info("Detecting bad practices for PR: {}", pullRequestId);

PullRequest pullRequest = pullRequestRepository.findById(pullRequestId).orElse(null);
if (pullRequest == null) {
throw new IllegalArgumentException("Pull request " + pullRequestId + " not found");
}

List<PullRequestBadPractice> detectedBadPractices = pullRequestBadPracticeDetector.detectAndSyncBadPractices(
pullRequest
);
return detectedBadPractices.stream().map(PullRequestBadPracticeDTO::fromPullRequestBadPractice).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package de.tum.in.www1.hephaestus.activity.badpracticedetector;

import de.tum.in.www1.hephaestus.gitprovider.label.Label;
import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequest;
import java.util.Set;
import org.springframework.stereotype.Component;

@Component
public class BadPracticeDetectorScheduler {

private static final String READY_TO_MERGE = "ready to merge";

public void detectBadPracticeForPrIfReadyToMerge(
PullRequest pullRequest,
Set<Label> oldLabels,
Set<Label> newLabels
) {
if (
newLabels.stream().anyMatch(label -> READY_TO_MERGE.equals(label.getName())) &&
oldLabels.stream().noneMatch(label -> READY_TO_MERGE.equals(label.getName()))
) {
BadPracticeDetectorTask badPracticeDetectorTask = new BadPracticeDetectorTask();
badPracticeDetectorTask.setPullRequest(pullRequest);
Thread thread = new Thread(badPracticeDetectorTask);
thread.start();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.tum.in.www1.hephaestus.activity.badpracticedetector;

import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequest;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Getter
@Setter
public class BadPracticeDetectorTask implements Runnable {

@Autowired
private PullRequestBadPracticeDetector pullRequestBadPracticeDetector;

private PullRequest pullRequest;

@Override
public void run() {
pullRequestBadPracticeDetector.detectAndSyncBadPractices(pullRequest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

import de.tum.in.www1.hephaestus.activity.PullRequestBadPracticeRepository;
import de.tum.in.www1.hephaestus.activity.model.PullRequestBadPractice;
import de.tum.in.www1.hephaestus.config.IntelligenceServiceConfig.BadPracticeDetectorService;
import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequest;
import de.tum.in.www1.hephaestus.intelligenceservice.model.BadPractice;
import de.tum.in.www1.hephaestus.intelligenceservice.model.DetectorRequest;
import de.tum.in.www1.hephaestus.intelligenceservice.model.DetectorResponse;
import jakarta.transaction.Transactional;
import java.util.LinkedList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -17,13 +23,67 @@ public class PullRequestBadPracticeDetector {
@Autowired
private PullRequestBadPracticeRepository pullRequestBadPracticeRepository;

@Autowired
private BadPracticeDetectorService detectorApi;

/**
* Detects bad practices in the given pull request and syncs them with the database.
* @param pullRequest The pull request to detect bad practices in.
* @return The detected bad practices.
*/
public List<PullRequestBadPractice> detectAndSyncBadPractices(PullRequest pullRequest) {
logger.info("Detecting bad practices for pull request: {}.", pullRequest.getId());
logger.info("Detecting bad practices for pull request: {}", pullRequest.getId());

List<PullRequestBadPractice> existingBadPractices = pullRequestBadPracticeRepository.findByPullRequestId(
pullRequest.getId()
);

return existingBadPractices;
DetectorRequest detectorRequest = new DetectorRequest();
detectorRequest.setDescription(pullRequest.getBody());
detectorRequest.setTitle(pullRequest.getTitle());
detectorRequest.setBadPractices(
existingBadPractices.stream().map(this::convertToIntelligenceBadPractice).toList()
);
DetectorResponse detectorResponse = detectorApi.detectDetectorPost(detectorRequest);

List<PullRequestBadPractice> detectedBadPractices = new LinkedList<>();

// Check if there are returned bad practices in the response with the same title as an existing bad practice
for (BadPractice badPractice : detectorResponse.getBadPractices()) {
boolean exists = false;
for (PullRequestBadPractice existingBadPractice : existingBadPractices) {
if (existingBadPractice.getTitle().equals(badPractice.getTitle())) {
existingBadPractice.setDescription(badPractice.getDescription());
existingBadPractice.setResolved(badPractice.getResolved());
detectedBadPractices.add(pullRequestBadPracticeRepository.save(existingBadPractice));
exists = true;
break;
}
}
if (!exists) {
detectedBadPractices.add(saveDetectedBadPractices(pullRequest, badPractice));
}
}

logger.info("Detected {} bad practices for pull request: {}", detectedBadPractices.size(), pullRequest.getId());
return detectedBadPractices;
}

protected PullRequestBadPractice saveDetectedBadPractices(PullRequest pullRequest, BadPractice badPractice) {

PullRequestBadPractice pullRequestBadPractice = new PullRequestBadPractice();
pullRequestBadPractice.setTitle(badPractice.getTitle());
pullRequestBadPractice.setDescription(badPractice.getDescription());
pullRequestBadPractice.setPullrequest(pullRequest);
pullRequestBadPractice.setResolved(badPractice.getResolved());
return pullRequestBadPracticeRepository.save(pullRequestBadPractice);
}

private BadPractice convertToIntelligenceBadPractice(PullRequestBadPractice pullRequestBadPractice) {
BadPractice badPractice = new BadPractice();
badPractice.setTitle(pullRequestBadPractice.getTitle());
badPractice.setDescription(pullRequestBadPractice.getDescription());
badPractice.setResolved(pullRequestBadPractice.isResolved());
return badPractice;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@

import de.tum.in.www1.hephaestus.gitprovider.pullrequest.PullRequest;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import jakarta.persistence.Table;
import lombok.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString
@Table(name = "pullrequestbadpractice")
public class PullRequestBadPractice {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NonNull
private String title;

@NonNull
private String description;

@NonNull
@ManyToOne
@JoinColumn(name = "pullrequest_id")
private PullRequest pullrequest;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.tum.in.www1.hephaestus.config;

import de.tum.in.www1.hephaestus.intelligenceservice.ApiClient;
import de.tum.in.www1.hephaestus.intelligenceservice.api.DetectorApi;
import de.tum.in.www1.hephaestus.intelligenceservice.api.MentorApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
Expand All @@ -23,4 +24,16 @@ public IntelligenceServiceApi() {
super(new ApiClient().setBasePath(intelligenceServiceUrl));
}
}

@Bean
public BadPracticeDetectorService badPracticeDetectorService() {
return new BadPracticeDetectorService();
}

public class BadPracticeDetectorService extends DetectorApi {

public BadPracticeDetectorService() {
super(new ApiClient().setBasePath(intelligenceServiceUrl));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.tum.in.www1.hephaestus.gitprovider.pullrequest;

import jakarta.transaction.Transactional;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
Expand All @@ -20,6 +21,7 @@ SELECT MIN(p.createdAt)
)
Optional<OffsetDateTime> firstContributionByAuthorLogin(@Param("authorLogin") String authorLogin);

@Transactional
@Query(
"""
SELECT p
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.tum.in.www1.hephaestus.gitprovider.pullrequest.github;

import de.tum.in.www1.hephaestus.activity.badpracticedetector.BadPracticeDetectorScheduler;
import de.tum.in.www1.hephaestus.gitprovider.common.DateUtil;
import de.tum.in.www1.hephaestus.gitprovider.label.Label;
import de.tum.in.www1.hephaestus.gitprovider.label.LabelRepository;
Expand Down Expand Up @@ -62,6 +63,9 @@ public class GitHubPullRequestSyncService {
@Autowired
private GitHubUserConverter userConverter;

@Autowired
private BadPracticeDetectorScheduler badPracticeDetectorScheduler;

/**
* Synchronizes all pull requests from the specified GitHub repositories.
*
Expand Down Expand Up @@ -233,6 +237,7 @@ public PullRequest processPullRequest(GHPullRequest ghPullRequest) {
});
resultLabels.add(resultLabel);
});
badPracticeDetectorScheduler.detectBadPracticeForPrIfReadyToMerge(result, result.getLabels(), resultLabels);
result.getLabels().clear();
result.getLabels().addAll(resultLabels);

Expand Down
Loading
Loading