Skip to content

Commit

Permalink
Merge branch 'develop' into feature/require-login
Browse files Browse the repository at this point in the history
  • Loading branch information
FelixTJDietrich committed Nov 20, 2024
2 parents f246b46 + 4131054 commit 9a018e2
Show file tree
Hide file tree
Showing 36 changed files with 611 additions and 125 deletions.
15 changes: 6 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@
],
"scripts": {
"generate:api:application-server:clean": "shx rm -rf webapp/src/app/core/modules/openapi",
"generate:api:application-server:specs": "cd server/application-server && mvn verify -DskipTests=true -Dapp.profiles=specs",
"generate:api:application-server:client": "npx openapi-generator-cli generate -i server/application-server/openapi.yaml -g typescript-angular -o webapp/src/app/core/modules/openapi --additional-properties fileNaming=kebab-case,withInterfaces=true --generate-alias-as-model",
"generate:api:application-server": "npm run generate:api:application-server:specs && npm run generate:api:application-server:clean && npm run generate:api:application-server:client",
"generate:api:intelligence-service:clean": "shx rm -rf server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice",
"generate:api:clean": "npm run generate:api:intelligence-service:clean && npm run generate:api:application-server:clean",
"generate:api:application-server-specs": "cd server/application-server && mvn verify -DskipTests=true -Dapp.profiles=specs",
"generate:api:intelligence-service-specs": "python -m server.intelligence-service.app.generate_openapi_yaml",
"generate:api:specs": "npm run generate:api:application-server-specs && npm run generate:api:intelligence-service-specs",
"generate:api:application-server-client": "npx openapi-generator-cli generate -i server/application-server/openapi.yaml -g typescript-angular -o webapp/src/app/core/modules/openapi --additional-properties fileNaming=kebab-case,withInterfaces=true --generate-alias-as-model",
"generate:api:intelligence-service-client": "npx openapi-generator-cli generate -i server/intelligence-service/openapi.yaml -g java --library resttemplate --api-package de.tum.in.www1.hephaestus.intelligenceservice.api --model-package de.tum.in.www1.hephaestus.intelligenceservice.model --invoker-package de.tum.in.www1.hephaestus.intelligenceservice --additional-properties useJakartaEe=true,performBeanValidation=true,generateClientAsBean=true,hideGenerationTimestamp=true --package-name de.tum.in.www1.hephaestus.intelligenceservice -o tmp/java-client && shx cp -r tmp/java-client/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice server/application-server/src/main/java/de/tum/in/www1/hephaestus && shx rm -rf tmp",
"generate:api:clients": "npm run generate:api:intelligence-service-client && npm run generate:api:application-server-client",
"generate:api:application-server": "npm run generate:api:application-server-specs && npm run generate:api:application-server:clean && npm run generate:api:application-server-client",
"generate:api:intelligence-service": "npm run generate:api:intelligence-service:clean && npm run generate:api:intelligence-service-specs && npm run generate:api:intelligence-service-client",
"generate:api:intelligence-service:specs": "python -m server.intelligence-service.app.generate_openapi_yaml",
"generate:api:intelligence-service:client": "npx openapi-generator-cli generate -i server/intelligence-service/openapi.yaml -g java --library resttemplate --api-package de.tum.in.www1.hephaestus.intelligenceservice.api --model-package de.tum.in.www1.hephaestus.intelligenceservice.model --invoker-package de.tum.in.www1.hephaestus.intelligenceservice --additional-properties useJakartaEe=true,performBeanValidation=true,generateClientAsBean=true,hideGenerationTimestamp=true --package-name de.tum.in.www1.hephaestus.intelligenceservice -o tmp/java-client && shx cp -r tmp/java-client/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice server/application-server/src/main/java/de/tum/in/www1/hephaestus && shx rm -rf tmp",
"generate:api:intelligence-service": "npm run generate:api:intelligence-service:clean && npm run generate:api:intelligence-service:specs && npm run generate:api:intelligence-service:client",
"generate:api": "npm run generate:api:intelligence-service && npm run generate:api:application-server",
"format:java:check": "prettier --check server/application-server/src/**/*.java",
"format:java:write": "prettier --write server/application-server/src/**/*.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2084,4 +2084,4 @@
"clientPolicies" : {
"policies" : [ ]
}
}
}
8 changes: 8 additions & 0 deletions server/application-server/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/AuthUserInfo"
/user:
delete:
tags:
- user
operationId: deleteUser
responses:
"200":
description: OK
components:
schemas:
PullRequestInfo:
Expand Down
5 changes: 5 additions & 0 deletions server/application-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>26.0.3</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,63 +44,99 @@ public OpenApiCustomizer schemaCustomizer() {
return openApi -> {
var components = openApi.getComponents();

// Only include schemas with DTO suffix and remove the suffix
var schemas = components
.getSchemas()
.entrySet()
.stream()
.filter(entry -> entry.getKey().endsWith("DTO"))
.collect(Collectors.toMap(entry -> entry.getKey().substring(0, entry.getKey().length() - 3),
if (components != null && components.getSchemas() != null) {
// Only include schemas with DTO suffix and remove the suffix
var schemas = components
.getSchemas()
.entrySet()
.stream()
.filter(entry -> entry.getKey().endsWith("DTO"))
.collect(Collectors.toMap(
entry -> entry.getKey().substring(0, entry.getKey().length() - 3),
entry -> {
var schema = entry.getValue();
schema.setName(entry.getKey().substring(0, entry.getKey().length() - 3));
return schema;
}));
}
));

// Remove DTO suffix from attribute names
schemas.forEach((key, value) -> {
Map<String, Schema<?>> properties = value.getProperties();
if (properties != null) {
properties.forEach((propertyKey, propertyValue) -> {
removeDTOSuffixesFromSchemaRecursively(propertyValue);
});
}
});
// Remove DTO suffix from attribute names
schemas.forEach((key, value) -> {
Map<String, Schema<?>> properties = value.getProperties();
if (properties != null) {
properties.forEach((propertyKey, propertyValue) -> {
removeDTOSuffixesFromSchemaRecursively(propertyValue);
});
}
});

components.setSchemas(schemas);
components.setSchemas(schemas);
} else {
logger.warn("Components or Schemas are null in OpenAPI configuration.");
}

var paths = openApi.getPaths();
paths.forEach((path, pathItem) -> {
logger.info(path);
pathItem.readOperations().forEach(operation -> {
// Remove DTO suffix from reponse schemas
var responses = operation.getResponses();
responses.forEach((responseCode, response) -> {
var content = response.getContent();
content.forEach((contentType, mediaType) -> {
removeDTOSuffixesFromSchemaRecursively(mediaType.getSchema());
if (paths != null) {
paths.forEach((path, pathItem) -> {
logger.info("Processing path: {}", path);
pathItem.readOperations().forEach(operation -> {
// Remove DTO suffix from response schemas
var responses = operation.getResponses();
if (responses != null) {
responses.forEach((responseCode, response) -> {
var content = response.getContent();
if (content != null) {
content.forEach((contentType, mediaType) -> {
if (mediaType != null && mediaType.getSchema() != null) {
removeDTOSuffixesFromSchemaRecursively(mediaType.getSchema());
} else {
logger.warn("MediaType or Schema is null for content type: {}", contentType);
}
});
} else {
logger.warn("Response with code {} has no content.", responseCode);
}
});
}

});
// Remove -controller suffix from tags
if (operation.getTags() != null) {
operation.setTags(
operation.getTags()
.stream()
.filter(tag -> {
if (tag.length() > 11) {
return true;
} else {
logger.warn("Tag '{}' is shorter than expected and cannot be trimmed.", tag);
return false;
}
})
.map(tag -> tag.substring(0, tag.length() - 11))
.collect(Collectors.toList())
);
}
});

// Remove -controller suffix from tags
operation.setTags(operation.getTags()
.stream()
.map(tag -> tag.substring(0, tag.length() - 11)).toList());
});
});


} else {
logger.warn("Paths are null in OpenAPI configuration.");
}
};
}

private void removeDTOSuffixesFromSchemaRecursively(Schema<?> schema) {
if (schema == null) {
return;
}

if (schema.get$ref() != null && schema.get$ref().endsWith("DTO")) {
schema.set$ref(schema.get$ref().substring(0, schema.get$ref().length() - 3));
String newRef = schema.get$ref().substring(0, schema.get$ref().length() - 3);
schema.set$ref(newRef);
logger.debug("Updated $ref from {} to {}", schema.get$ref(), newRef);
}

if (schema.getItems() != null) {
removeDTOSuffixesFromSchemaRecursively(schema.getItems());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.tum.in.www1.hephaestus.config;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;


@Configuration
public class KeycloakConfig {

@Value("${keycloak.url}")
private String authServerUrl;

@Value("${keycloak.realm}")
private String realm;

@Value("${keycloak.client-id}")
private String clientId;

@Value("${keycloak.client-secret}")
private String clientSecret;

@Bean
public Keycloak keycloakClient() {
return KeycloakBuilder.builder()
.serverUrl(authServerUrl)
.realm(realm)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.clientId(clientId)
.clientSecret(clientSecret)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
@Configuration
public class NatsConfig {

@Value("${nats.enabled}")
private boolean isNatsEnabled;

@Value("${nats.server}")
private String natsServer;

Expand All @@ -21,7 +24,7 @@ public class NatsConfig {

@Bean
public Connection natsConnection() throws Exception {
if (environment.matchesProfiles("specs")) {
if (environment.matchesProfiles("specs") || !isNatsEnabled) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package de.tum.in.www1.hephaestus.gitprovider.user;

import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.UsersResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -11,7 +19,16 @@
@RestController
@RequestMapping("/user")
public class UserController {
private final UserService userService;

private static final Logger logger = LoggerFactory.getLogger(UserController.class);

@Autowired
private UserService userService;
@Autowired
private Keycloak keycloak;
@Value("${keycloak.realm}")
private String realm;


public UserController(UserService actorService) {
this.userService = actorService;
Expand All @@ -22,4 +39,21 @@ public ResponseEntity<UserProfileDTO> getUserProfile(@PathVariable String login)
Optional<UserProfileDTO> userProfile = userService.getUserProfile(login);
return userProfile.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
}

@DeleteMapping
public ResponseEntity<Void> deleteUser(JwtAuthenticationToken auth) {
if (auth == null) {
logger.error("No authentication token found.");
return ResponseEntity.badRequest().body(null);
}

String userId = auth.getToken().getClaimAsString(StandardClaimNames.SUB);
logger.info("Deleting user {}", userId);
var response = keycloak.realm(realm).users().delete(userId);
if (response.getStatus() != 204) {
logger.error("Failed to delete user account: {}", response.getStatusInfo().getReasonPhrase());
return ResponseEntity.badRequest().body(null);
}
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,39 +170,34 @@ private int calculateTotalScore(List<PullRequestReview> reviews, int numberOfIss
// All reviews are for the same pull request
int complexityScore = calculateComplexityScore(pullRequestReviews.get(0).getPullRequest());

int approvalReviews = (int) pullRequestReviews
double approvalWeight = 2.0;
double approvalScore = pullRequestReviews
.stream()
.filter(review -> review.getState() == PullRequestReview.State.APPROVED)
.count();
int changesRequestedReviews = (int) pullRequestReviews
.filter(review -> review.getAuthor().getId() != review.getPullRequest().getAuthor().getId())
.map(review -> approvalWeight * calculateCodeReviewBonus(review.getComments().size(), complexityScore))
.reduce(0.0, Double::sum);

double changesRequestedWeight = 2.5;
double changesRequestedScore = pullRequestReviews
.stream()
.filter(review -> review.getState() == PullRequestReview.State.CHANGES_REQUESTED)
.count();
int commentReviews = (int) pullRequestReviews
.stream()
.filter(review -> review.getState() == PullRequestReview.State.COMMENTED)
.filter(review -> review.getBody() != null) // Only count if there is a comment
.count();
int unknownReviews = (int) pullRequestReviews
.stream()
.filter(review -> review.getState() == PullRequestReview.State.UNKNOWN)
.filter(review -> review.getBody() != null) // Only count if there is a comment
.count();
.filter(review -> review.getAuthor().getId() != review.getPullRequest().getAuthor().getId())
.map(review -> changesRequestedWeight * calculateCodeReviewBonus(review.getComments().size(), complexityScore))
.reduce(0.0, Double::sum);

int codeComments = pullRequestReviews
double commentWeight = 1.5;
double commentScore = pullRequestReviews
.stream()
.map(review -> review.getComments().size())
.reduce(0, Integer::sum);

double interactionScore =
(approvalReviews * 1.5 +
changesRequestedReviews * 2.0 +
(commentReviews + unknownReviews + numberOfIssueComments) +
codeComments * 0.5);
.filter(review -> review.getState() == PullRequestReview.State.COMMENTED || review.getState() == PullRequestReview.State.UNKNOWN)
.filter(review -> review.getAuthor().getId() != review.getPullRequest().getAuthor().getId())
.map(review -> commentWeight * calculateCodeReviewBonus(review.getComments().size(), complexityScore))
.reduce(0.0, Double::sum);

double complexityBonus = 1 + (complexityScore - 1) / 32.0;
double issueCommentScore = commentWeight * numberOfIssueComments;

return 5 * interactionScore * complexityBonus;
double interactionScore = approvalScore + changesRequestedScore + commentScore + issueCommentScore;
return 10 * interactionScore * complexityScore / (interactionScore + complexityScore);
})
.reduce(0.0, Double::sum);
return (int) Math.ceil(totalScore);
Expand Down Expand Up @@ -234,4 +229,27 @@ private int calculateComplexityScore(PullRequest pullRequest) {
}
return 33; // Overly complex
}

/**
* Calculates the code review bonus for a given number of code comments and complexity score.
* The bonus is a value between 0 and 2.
* Taken from the original leaderboard implementation script.
*
* @param codeComments
* @param complexityScore
* @return bonus
*/
private double calculateCodeReviewBonus(int codeComments, int complexityScore) {
double maxBonus = 2;

double codeReviewBonus = 1;
if (codeComments < complexityScore) {
// Function goes from 0 at codeComments = 0 to 1 at codeComments = complexityScore
codeReviewBonus += 2 * Math.sqrt(complexityScore) * Math.sqrt(codeComments) / (codeComments + complexityScore);
} else {
// Saturate at 1
codeReviewBonus += 1;
}
return codeReviewBonus / 2 * maxBonus;
}
}
Loading

0 comments on commit 9a018e2

Please sign in to comment.