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

[Feature] Add Custom Feedbacks Support #18

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.tum.in.www1</groupId>
<artifactId>bamboo-server</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
<organization>
<name>LS1 TUM</name>
<url>https://ase.in.tum.de</url>
Expand All @@ -30,7 +30,7 @@
<atlassian.spring.scanner.version>1.2.13</atlassian.spring.scanner.version>
<platform.version>4.0.0</platform.version>
<jackson.version>2.13.2</jackson.version>
<spotless.version>2.21.0</spotless.version>
<spotless.version>2.27.2</spotless.version>
<junit-jupiter.version>5.8.2</junit-jupiter.version>
<mockito.version>4.3.1</mockito.version>
<!-- This key is used to keep the consistency between the key in atlassian-plugin.xml and the key to generate bundle. -->
Expand Down
182 changes: 182 additions & 0 deletions src/main/java/de/tum/in/www1/bamboo/server/CustomFeedbackParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package de.tum.in.www1.bamboo.server;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import model.CustomFeedback;
import model.TestResultsWithSuccessInfo;

import org.apache.log4j.Logger;

import com.atlassian.bamboo.artifact.MutableArtifact;
import com.atlassian.bamboo.build.BuildLoggerManager;
import com.atlassian.bamboo.build.artifact.ArtifactLink;
import com.atlassian.bamboo.build.artifact.ArtifactLinkDataProvider;
import com.atlassian.bamboo.build.artifact.ArtifactLinkManager;
import com.atlassian.bamboo.build.artifact.FileSystemArtifactLinkDataProvider;
import com.atlassian.bamboo.build.logger.BuildLogger;
import com.atlassian.bamboo.plan.cache.ImmutablePlan;
import com.atlassian.bamboo.resultsummary.tests.TestCaseResultError;
import com.atlassian.bamboo.resultsummary.tests.TestCaseResultErrorImpl;
import com.fasterxml.jackson.databind.ObjectMapper;

public class CustomFeedbackParser {

private static final Logger log = Logger.getLogger(CustomFeedbackParser.class);

// The label of the artifact defined in the Bamboo build plan
private static final String CUSTOM_FEEDBACK_ARTIFACT_LABEL = "customFeedbacks";

private final ArtifactLinkManager artifactLinkManager;

private final BuildLoggerManager buildLoggerManager;

private final ImmutablePlan plan;

public CustomFeedbackParser(ArtifactLinkManager artifactLinkManager, BuildLoggerManager buildLoggerManager, ImmutablePlan plan) {
this.artifactLinkManager = artifactLinkManager;
this.buildLoggerManager = buildLoggerManager;
this.plan = plan;
}

/**
* Parses custom feedback from the Bamboo artifact.
*
* @param artifactLinks the ArtifactLinks of the Bamboo build
* @param jobId the job id
*/
public Collection<TestResultsWithSuccessInfo> extractCustomFeedbacks(Collection<ArtifactLink> artifactLinks, long jobId) {
// Get the data provider for custom feedbacks so that we can parse the .json files
Optional<FileSystemArtifactLinkDataProvider> dataProvider = getCustomFeedbacksDataProvider(artifactLinks, jobId);
if (!dataProvider.isPresent()) {
return new ArrayList<>();
}

return parseCustomFeedbacks(dataProvider.get().getFile());
}

private Optional<FileSystemArtifactLinkDataProvider> getCustomFeedbacksDataProvider(Collection<ArtifactLink> artifactLinks, long jobId) {
// Custom feedbacks are located in an artifact labeled by CUSTOM_FEEDBACK_ARTIFACT_LABEL
Optional<ArtifactLink> customFeedbackArtifactLink = artifactLinks.stream()
.filter(artifactLink -> artifactLink.getArtifact().getLabel().equals(CUSTOM_FEEDBACK_ARTIFACT_LABEL)).findFirst();
if (!customFeedbackArtifactLink.isPresent()) {
return Optional.empty();
}

MutableArtifact artifact = customFeedbackArtifactLink.get().getArtifact();

ArtifactLinkDataProvider dataProvider = artifactLinkManager.getArtifactLinkDataProvider(artifact);
if (dataProvider == null) {
logToDebugAndBuildLog("Could not retrieve data for artifact " + artifact.getLabel() + " in job " + jobId);
return Optional.empty();
}

if (dataProvider instanceof FileSystemArtifactLinkDataProvider) {
FileSystemArtifactLinkDataProvider fileDataProvider = (FileSystemArtifactLinkDataProvider) dataProvider;
return Optional.of(fileDataProvider);
}
else {
logToDebugAndBuildLog("Unsupported artifact handler configuration encountered for artifact " + artifact.getLabel() + " in job " + jobId);
return Optional.empty();
}
}

/**
* Parses custom feedback json files into CustomFeedback objects if they are present
* within the directory.
*
* @param customFeedbacksDir directory that has the custom feedback json files
* @return a collection of CustomFeedbacks
*/
private Collection<TestResultsWithSuccessInfo> parseCustomFeedbacks(File customFeedbacksDir) {
if (customFeedbacksDir == null || !customFeedbacksDir.isDirectory()) {
return new ArrayList<>();
}

File[] files = customFeedbacksDir.listFiles();
if (files == null) {
return new ArrayList<>();
}

Collection<CustomFeedback> customFeedbacks = Arrays.stream(files)
.filter(file -> file.getName().endsWith(".json"))
.map(this::parseCustomFeedback)
.filter(Optional::isPresent)
.map(Optional::get).collect(Collectors.toList());

return toTestResults(customFeedbacks);
}

/**
* Parses a custom feedback file into an object.
*
* @param customFeedbackFile file containing the custom feedback json
* @return the CustomFeedback
*/
private Optional<CustomFeedback> parseCustomFeedback(File customFeedbackFile) {
try {
ObjectMapper mapper = new ObjectMapper();
CustomFeedback feedback = mapper.readValue(customFeedbackFile, CustomFeedback.class);
if (feedback.getMessage() != null && feedback.getMessage().trim().isEmpty()) {
feedback.setMessage(null);
}
validateCustomFeedback(customFeedbackFile.getName(), feedback);
return Optional.of(feedback);
}
catch (IOException e) {
logToDebugAndBuildLog("Cannot parse custom feedback for file " + customFeedbackFile.getName() + ": " + e.getMessage());
return Optional.empty();
}
}

/**
* Checks that the custom feedback has a valid format
* <p>
* A custom feedback has to have a non-empty, non only-whitespace name to be able to identify it in Artemis.
* If it is not successful, there has to be a message explaining a reason why this is the case.
*
* @param fileName where the custom feedback was read from.
* @param feedback the custom feedback to validate.
* @throws InvalidPropertiesFormatException if one of the invariants described above does not hold.
*/
private static void validateCustomFeedback(final String fileName, final CustomFeedback feedback) throws InvalidPropertiesFormatException {
if (feedback.getName() == null || feedback.getName().trim().isEmpty()) {
throw new InvalidPropertiesFormatException(String.format("Custom feedback from file %s needs to have a name attribute.", fileName));
}
if (!feedback.isSuccessful() && feedback.getMessage() == null) {
throw new InvalidPropertiesFormatException(String.format("Custom non-success feedback from file %s needs to have a message", fileName));
}
}

/**
* Converts the custom feedback objects into Bamboo TestResults objects.
*
* @param customFeedbacks the custom feedbacks
* @return the TestResults objects
*/
private Collection<TestResultsWithSuccessInfo> toTestResults(Collection<CustomFeedback> customFeedbacks) {
return customFeedbacks.stream().map(customFeedback -> {
String name = customFeedback.getName();
TestResultsWithSuccessInfo testResults = new TestResultsWithSuccessInfo(name, name);

if (customFeedback.isSuccessful()) {
testResults.setSuccessMessage(customFeedback.getMessage());
}
else {
TestCaseResultError error = new TestCaseResultErrorImpl(customFeedback.getMessage());
testResults.addError(error);
}
return testResults;
}).collect(Collectors.toList());
}

private void logToDebugAndBuildLog(String message) {
log.debug(message);
if (buildLoggerManager != null && plan != null) {
BuildLogger buildLogger = buildLoggerManager.getLogger(plan.getPlanKey());
buildLogger.addBuildLogEntry("[BAMBOO-SERVER-NOTIFICATION] " + message);
}
}
}
11 changes: 8 additions & 3 deletions src/main/java/de/tum/in/www1/bamboo/server/LoggingUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@

public class LoggingUtils {

private LoggingUtils() {
}

private static final String APPLICATION_NAME = "[BAMBOO-SERVER-NOTIFICATION] ";

public static void logDebug(String message, PlanKey planKey, Logger log) {
String planKeyString = planKey != null ? "[" + planKey + "] " : "";
String logMessage = "[BAMBOO-SERVER-NOTIFICATION] " + planKeyString + message;
String logMessage = APPLICATION_NAME + planKeyString + message;
log.debug(logMessage);
}

public static void logInfo(String message, BuildLoggerManager buildLoggerManager, PlanKey planKey, Logger log) {
String planKeyString = planKey != null ? "[" + planKey + "] " : "";
String logMessage = "[BAMBOO-SERVER-NOTIFICATION] " + planKeyString + message;
String logMessage = APPLICATION_NAME + planKeyString + message;
if (buildLoggerManager != null && planKey != null) {
BuildLogger buildLogger = buildLoggerManager.getLogger(planKey);
buildLogger.addBuildLogEntry(logMessage);
Expand All @@ -26,7 +31,7 @@ public static void logInfo(String message, BuildLoggerManager buildLoggerManager

public static void logError(String message, BuildLoggerManager buildLoggerManager, PlanKey planKey, Logger log, Exception e) {
String planKeyString = planKey != null ? "[" + planKey + "] " : "";
String logMessage = "[BAMBOO-SERVER-NOTIFICATION] " + planKeyString + message;
String logMessage = APPLICATION_NAME + planKeyString + message;
if (buildLoggerManager != null && planKey != null) {
BuildLogger buildLogger = buildLoggerManager.getLogger(planKey);
buildLogger.addErrorLogEntry(logMessage);
Expand Down
Loading