Skip to content

Commit

Permalink
Merge branch 'develop' into chore/communication/migrate-metis-service…
Browse files Browse the repository at this point in the history
…-to-standalone
  • Loading branch information
cremertim authored Dec 4, 2024
2 parents ede5a31 + 0f12a54 commit 2462f23
Show file tree
Hide file tree
Showing 68 changed files with 1,059 additions and 152 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,13 @@ jacocoTestCoverageVerification {
counter = "INSTRUCTION"
value = "COVEREDRATIO"
// TODO: in the future the following value should become higher than 0.92
minimum = 0.893
minimum = 0.892
}
limit {
counter = "CLASS"
value = "MISSEDCOUNT"
// TODO: in the future the following value should become less than 10
maximum = 64
maximum = 65
}
}
}
Expand Down
26 changes: 25 additions & 1 deletion docs/user/exercises/programming-exercise-setup.inc
Original file line number Diff line number Diff line change
Expand Up @@ -404,14 +404,38 @@ Edit Maximum Build Duration
^^^^^^^^^^^^^^^^^^^^^^^^^^^

**This option is only available when using** :ref:`integrated code lifecycle<integrated code lifecycle>`
This section is optional. In most cases, the preconfigured build script does not need to be changed.

This section is optional. In most cases, the default maximum build duration does not need to be changed.

The maximum build duration is the time limit for the build plan to execute. If the build plan exceeds this time limit, it will be terminated. The default value is 120 seconds.
You can change the maximum build duration by using the slider.

.. figure:: programming/timeout-slider.png
:align: center

Edit Container Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

**This option is only available when using** :ref:`integrated code lifecycle<integrated code lifecycle>`

This section is optional. In most cases, the default container configuration does not need to be changed.

Currently, instructors can only change whether the container has internet access and add additional environment variables.
Disabling internet access can be useful if instructors want to prevent students from downloading additional dependencies during the build process.
If internet access is disabled, the container cannot access the internet during the build process. Thus, it will not be able to download additional dependencies.
The dependencies must then be included/cached in the docker image.

Additional environment variables can be added to the container configuration. This can be useful if the build process requires additional environment variables to be set.

.. figure:: programming/docker-flags-edit.png
:align: center

We plan to add more options to the container configuration in the future.

.. warning::
- Disabling internet access is not currently supported for Swift and Haskell exercises.


.. _configure_static_code_analysis_tools:

Configure static code analysis
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ module.exports = {
coverageThreshold: {
global: {
// TODO: in the future, the following values should increase to at least 90%
statements: 87.72,
branches: 73.83,
functions: 82.29,
lines: 87.78,
statements: 87.69,
branches: 73.79,
functions: 82.27,
lines: 87.74,
},
},
coverageReporters: ['clover', 'json', 'lcov', 'text-summary'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record BuildConfig(String buildScript, String dockerImage, String commitHashToBuild, String assignmentCommitHash, String testCommitHash, String branch,
ProgrammingLanguage programmingLanguage, ProjectType projectType, boolean scaEnabled, boolean sequentialTestRunsEnabled, boolean testwiseCoverageEnabled,
List<String> resultPaths, int timeoutSeconds, String assignmentCheckoutPath, String testCheckoutPath, String solutionCheckoutPath) implements Serializable {
List<String> resultPaths, int timeoutSeconds, String assignmentCheckoutPath, String testCheckoutPath, String solutionCheckoutPath, DockerRunConfig dockerRunConfig)
implements Serializable {

@Override
public String dockerImage() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package de.tum.cit.aet.artemis.buildagent.dto;

import java.util.Map;

public record DockerFlagsDTO(String network, Map<String, String> env) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.tum.cit.aet.artemis.buildagent.dto;

import java.io.Serializable;
import java.util.List;

public record DockerRunConfig(boolean isNetworkDisabled, List<String> env) implements Serializable {
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,23 @@ public BuildJobContainerService(DockerClient dockerClient, HostConfig hostConfig
/**
* Configure a container with the Docker image, the container name, optional proxy config variables, and set the command that runs when the container starts.
*
* @param containerName the name of the container to be created
* @param image the Docker image to use for the container
* @param buildScript the build script to be executed in the container
* @param containerName the name of the container to be created
* @param image the Docker image to use for the container
* @param buildScript the build script to be executed in the container
* @param exerciseEnvVars the environment variables provided by the instructor
* @return {@link CreateContainerResponse} that can be used to start the container
*/
public CreateContainerResponse configureContainer(String containerName, String image, String buildScript) {
public CreateContainerResponse configureContainer(String containerName, String image, String buildScript, List<String> exerciseEnvVars) {
List<String> envVars = new ArrayList<>();
if (useSystemProxy) {
envVars.add("HTTP_PROXY=" + httpProxy);
envVars.add("HTTPS_PROXY=" + httpsProxy);
envVars.add("NO_PROXY=" + noProxy);
}
envVars.add("SCRIPT=" + buildScript);
if (exerciseEnvVars != null && !exerciseEnvVars.isEmpty()) {
envVars.addAll(exerciseEnvVars);
}
return dockerClient.createContainerCmd(image).withName(containerName).withHostConfig(hostConfig).withEnv(envVars)
// Command to run when the container starts. This is the command that will be executed in the container's main process, which runs in the foreground and blocks the
// container from exiting until it finishes.
Expand All @@ -121,11 +125,23 @@ public void startContainer(String containerId) {
/**
* Run the script in the container and wait for it to finish before returning.
*
* @param containerId the id of the container in which the script should be run
* @param buildJobId the id of the build job that is currently being executed
* @param containerId the id of the container in which the script should be run
* @param buildJobId the id of the build job that is currently being executed
* @param isNetworkDisabled whether the network should be disabled for the container
*/
public void runScriptInContainer(String containerId, String buildJobId, boolean isNetworkDisabled) {
if (isNetworkDisabled) {
log.info("disconnecting container with id {} from network", containerId);
try {
dockerClient.disconnectFromNetworkCmd().withContainerId(containerId).withNetworkId("bridge").exec();
}
catch (Exception e) {
log.error("Failed to disconnect container with id {} from network: {}", containerId, e.getMessage());
buildLogsMap.appendBuildLogEntry(buildJobId, "Failed to disconnect container from default network 'bridge': " + e.getMessage());
throw new LocalCIException("Failed to disconnect container from default network 'bridge': " + e.getMessage());
}
}

public void runScriptInContainer(String containerId, String buildJobId) {
log.info("Started running the build script for build job in container with id {}", containerId);
// The "sh script.sh" execution command specified here is run inside the container as an additional process. This command runs in the background, independent of the
// container's
Expand Down Expand Up @@ -448,9 +464,4 @@ private Container getContainerForName(String containerName) {
List<Container> containers = dockerClient.listContainersCmd().withShowAll(true).exec();
return containers.stream().filter(container -> container.getNames()[0].equals("/" + containerName)).findFirst().orElse(null);
}

private String getParentFolderPath(String path) {
Path parentPath = Paths.get(path).normalize().getParent();
return parentPath != null ? parentPath.toString() : "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,18 @@ public BuildResult runBuildJob(BuildJobQueueItem buildJob, String containerName)
index++;
}

CreateContainerResponse container = buildJobContainerService.configureContainer(containerName, buildJob.buildConfig().dockerImage(), buildJob.buildConfig().buildScript());
List<String> envVars = null;
boolean isNetworkDisabled = false;
if (buildJob.buildConfig().dockerRunConfig() != null) {
envVars = buildJob.buildConfig().dockerRunConfig().env();
isNetworkDisabled = buildJob.buildConfig().dockerRunConfig().isNetworkDisabled();
}

CreateContainerResponse container = buildJobContainerService.configureContainer(containerName, buildJob.buildConfig().dockerImage(), buildJob.buildConfig().buildScript(),
envVars);

return runScriptAndParseResults(buildJob, containerName, container.getId(), assignmentRepoUri, testsRepoUri, solutionRepoUri, auxiliaryRepositoriesUris,
assignmentRepositoryPath, testsRepositoryPath, solutionRepositoryPath, auxiliaryRepositoriesPaths, assignmentCommitHash, testCommitHash);
assignmentRepositoryPath, testsRepositoryPath, solutionRepositoryPath, auxiliaryRepositoriesPaths, assignmentCommitHash, testCommitHash, isNetworkDisabled);
}

/**
Expand Down Expand Up @@ -270,7 +278,7 @@ public BuildResult runBuildJob(BuildJobQueueItem buildJob, String containerName)
private BuildResult runScriptAndParseResults(BuildJobQueueItem buildJob, String containerName, String containerId, VcsRepositoryUri assignmentRepositoryUri,
VcsRepositoryUri testRepositoryUri, VcsRepositoryUri solutionRepositoryUri, VcsRepositoryUri[] auxiliaryRepositoriesUris, Path assignmentRepositoryPath,
Path testsRepositoryPath, Path solutionRepositoryPath, Path[] auxiliaryRepositoriesPaths, @Nullable String assignmentRepoCommitHash,
@Nullable String testRepoCommitHash) {
@Nullable String testRepoCommitHash, boolean isNetworkDisabled) {

long timeNanoStart = System.nanoTime();

Expand All @@ -292,7 +300,7 @@ private BuildResult runScriptAndParseResults(BuildJobQueueItem buildJob, String
buildLogsMap.appendBuildLogEntry(buildJob.id(), msg);
log.debug(msg);

buildJobContainerService.runScriptInContainer(containerId, buildJob.id());
buildJobContainerService.runScriptInContainer(containerId, buildJob.id(), isNetworkDisabled);

msg = "~~~~~~~~~~~~~~~~~~~~ Finished Executing Build Script for Build job " + buildJob.id() + " ~~~~~~~~~~~~~~~~~~~~";
buildLogsMap.appendBuildLogEntry(buildJob.id(), msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,16 +242,18 @@ public static GroupNotification createAnnouncementNotification(Post post, User a
GroupNotification notification;
title = NotificationConstants.NEW_ANNOUNCEMENT_POST_TITLE;
text = NotificationConstants.NEW_ANNOUNCEMENT_POST_TEXT;
var imageUrl = post.getAuthor().getImageUrl() == null ? "" : post.getAuthor().getImageUrl();
placeholderValues = createPlaceholdersNewAnnouncementPost(course.getTitle(), post.getTitle(), Jsoup.parse(post.getContent()).text(), post.getCreationDate().toString(),
post.getAuthor().getName());
post.getAuthor().getName(), imageUrl, post.getAuthor().getId().toString(), post.getId().toString());
notification = new GroupNotification(course, title, text, true, placeholderValues, author, groupNotificationType);
notification.setTransientAndStringTarget(createCoursePostTarget(post, course));
return notification;
}

@NotificationPlaceholderCreator(values = { NEW_ANNOUNCEMENT_POST })
public static String[] createPlaceholdersNewAnnouncementPost(String courseTitle, String postTitle, String postContent, String postCreationDate, String postAuthorName) {
return new String[] { courseTitle, postTitle, postContent, postCreationDate, postAuthorName };
public static String[] createPlaceholdersNewAnnouncementPost(String courseTitle, String postTitle, String postContent, String postCreationDate, String postAuthorName,
String imageUrl, String authorId, String postId) {
return new String[] { courseTitle, postTitle, postContent, postCreationDate, postAuthorName, imageUrl, authorId, postId };
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,9 +321,11 @@ public static SingleUserNotification createNotification(AnswerPost answerPost, N
}

Conversation conversation = answerPost.getPost().getConversation();
var imageUrl = answerPost.getAuthor().getImageUrl() != null ? answerPost.getAuthor().getImageUrl() : "";
var placeholders = createPlaceholdersNewReply(conversation.getCourse().getTitle(), answerPost.getPost().getContent(), answerPost.getPost().getCreationDate().toString(),
answerPost.getPost().getAuthor().getName(), answerPost.getContent(), answerPost.getCreationDate().toString(), answerPost.getAuthor().getName(),
conversation.getHumanReadableNameForReceiver(answerPost.getAuthor()));
conversation.getHumanReadableNameForReceiver(answerPost.getAuthor()), imageUrl, answerPost.getAuthor().getId().toString(), answerPost.getId().toString(),
answerPost.getPost().getId().toString());

String messageReplyTextType = MESSAGE_REPLY_IN_CONVERSATION_TEXT;

Expand All @@ -340,8 +342,9 @@ public static SingleUserNotification createNotification(AnswerPost answerPost, N
@NotificationPlaceholderCreator(values = { NEW_REPLY_FOR_EXERCISE_POST, NEW_REPLY_FOR_LECTURE_POST, NEW_REPLY_FOR_COURSE_POST, NEW_REPLY_FOR_EXAM_POST,
CONVERSATION_NEW_REPLY_MESSAGE, CONVERSATION_USER_MENTIONED })
public static String[] createPlaceholdersNewReply(String courseTitle, String postContent, String postCreationData, String postAuthorName, String answerPostContent,
String answerPostCreationDate, String authorName, String conversationName) {
return new String[] { courseTitle, postContent, postCreationData, postAuthorName, answerPostContent, answerPostCreationDate, authorName, conversationName };
String answerPostCreationDate, String authorName, String conversationName, String imageUrl, String userId, String postingId, String parentPostId) {
return new String[] { courseTitle, postContent, postCreationData, postAuthorName, answerPostContent, answerPostCreationDate, authorName, conversationName, imageUrl, userId,
postingId, parentPostId };
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.tum.cit.aet.artemis.communication.domain.push_notification;

import java.util.Arrays;

public enum PushNotificationApiType {

DEFAULT((short) 0), IOS_V2((short) 1);

private final short databaseKey;

PushNotificationApiType(short databaseKey) {
this.databaseKey = databaseKey;
}

public short getDatabaseKey() {
return databaseKey;
}

public static PushNotificationApiType fromDatabaseKey(short databaseKey) {
return Arrays.stream(PushNotificationApiType.values()).filter(type -> type.getDatabaseKey() == databaseKey).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown database key: " + databaseKey));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
Expand Down Expand Up @@ -34,6 +35,10 @@ public class PushNotificationDeviceConfiguration {
@Column(name = "device_type")
private PushNotificationDeviceType deviceType;

@Enumerated
@Column(name = "api_type")
private PushNotificationApiType apiType;

@Column(name = "expiration_date")
private Date expirationDate;

Expand All @@ -53,6 +58,16 @@ public PushNotificationDeviceConfiguration(String token, PushNotificationDeviceT
this.owner = owner;
}

public PushNotificationDeviceConfiguration(String token, PushNotificationDeviceType deviceType, Date expirationDate, byte[] secretKey, User owner,
PushNotificationApiType apiType) {
this.token = token;
this.deviceType = deviceType;
this.expirationDate = expirationDate;
this.secretKey = secretKey;
this.owner = owner;
this.apiType = apiType;
}

public PushNotificationDeviceConfiguration() {
// needed for JPA
}
Expand Down Expand Up @@ -97,6 +112,10 @@ public void setOwner(User owner) {
this.owner = owner;
}

public PushNotificationApiType getApiType() {
return apiType;
}

@Override
public boolean equals(Object object) {
if (this == object) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package de.tum.cit.aet.artemis.communication.dto;

import de.tum.cit.aet.artemis.communication.domain.push_notification.PushNotificationApiType;
import de.tum.cit.aet.artemis.communication.domain.push_notification.PushNotificationDeviceType;

public record PushNotificationRegisterBody(String token, PushNotificationDeviceType deviceType) {
public record PushNotificationRegisterBody(String token, PushNotificationDeviceType deviceType, PushNotificationApiType apiType) {

public PushNotificationRegisterBody(String token, PushNotificationDeviceType deviceType) {
this(token, deviceType, PushNotificationApiType.DEFAULT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,19 @@ public ConversationNotification createNotification(Post createdMessage, Conversa
}
default -> throw new IllegalStateException("Unexpected value: " + conversation);
}
var imageUrl = createdMessage.getAuthor().getImageUrl() == null ? "" : createdMessage.getAuthor().getImageUrl();
String[] placeholders = createPlaceholdersNewMessageChannelText(course.getTitle(), createdMessage.getContent(), createdMessage.getCreationDate().toString(),
conversationName, createdMessage.getAuthor().getName(), conversationType);
conversationName, createdMessage.getAuthor().getName(), conversationType, imageUrl, createdMessage.getAuthor().getId().toString(),
createdMessage.getId().toString());
ConversationNotification notification = createConversationMessageNotification(course.getId(), createdMessage, notificationType, notificationText, true, placeholders);
save(notification, mentionedUsers, placeholders, createdMessage);
return notification;
}

@NotificationPlaceholderCreator(values = { CONVERSATION_NEW_MESSAGE })
public static String[] createPlaceholdersNewMessageChannelText(String courseTitle, String messageContent, String messageCreationDate, String conversationName,
String authorName, String conversationType) {
return new String[] { courseTitle, messageContent, messageCreationDate, conversationName, authorName, conversationType };
String authorName, String conversationType, String imageUrl, String userId, String postId) {
return new String[] { courseTitle, messageContent, messageCreationDate, conversationName, authorName, conversationType, imageUrl, userId, postId };
}

private void save(ConversationNotification notification, Set<User> mentionedUsers, String[] placeHolders, Post createdMessage) {
Expand Down
Loading

0 comments on commit 2462f23

Please sign in to comment.