diff --git a/Dockerfile b/Dockerfile index a44b1c0462..301f2d2f64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,25 @@ -FROM alpine:latest -LABEL version=5.9.1 description="EPAM Report portal. Main API Service" maintainer="Andrei Varabyeu , Hleb Kanonik " -ARG GH_TOKEN -RUN echo 'exec java ${JAVA_OPTS} -jar service-api-5.9.1-exec.jar' > /start.sh && chmod +x /start.sh && \ - wget --header="Authorization: Bearer ${GH_TOKEN}" -q https://maven.pkg.github.com/reportportal/service-api/com/epam/reportportal/service-api/5.9.1/service-api-5.9.1-exec.jar -ENV JAVA_OPTS="-Xmx1g -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=70 -Djava.security.egd=file:/dev/./urandom" +FROM gradle:6.8.3-jdk11 AS build +ARG BOM_VERSION MIGRATION_VERSION GITHUB_USER GITHUB_TOKEN RELEASE_MODE SCRIPTS_VERSION APP_VERSION +WORKDIR /usr/app +COPY . /usr/app +RUN if [ ${RELEASE_MODE} = true ]; then \ + gradle build --exclude-task test \ + -PreleaseMode=true \ + -PgithubUserName=${GITHUB_USER} \ + -PgithubToken=${GITHUB_TOKEN} \ + -Pscripts.version=${SCRIPTS_VERSION} \ + -Pmigrations.version=${MIGRATION_VERSION} \ + -Pbom.version=${BOM_VERSION} \ + -Dorg.gradle.project.version=${APP_VERSION}; \ + else gradle build --exclude-task test -Dorg.gradle.project.version=${APP_VERSION}; fi + +# For ARM build use flag: `--platform linux/arm64` +FROM --platform=$BUILDPLATFORM amazoncorretto:11.0.19 +LABEL version=${APP_VERSION} description="EPAM Report portal. Main API Service" maintainer="Andrei Varabyeu , Hleb Kanonik " +ARG APP_VERSION=${APP_VERSION} +ENV APP_DIR=/usr/app JAVA_OPTS="-Xmx1g -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=70 -Djava.security.egd=file:/dev/./urandom" +WORKDIR $APP_DIR +COPY --from=build $APP_DIR/build/libs/service-api-*exec.jar . VOLUME ["/tmp"] EXPOSE 8080 -ENTRYPOINT ./start.sh +ENTRYPOINT exec java ${JAVA_OPTS} -jar ${APP_DIR}/service-api-*exec.jar \ No newline at end of file diff --git a/build.gradle b/build.gradle index d735e6714f..cffdd18f58 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { compile 'com.epam.reportportal:plugin-api' } else { compile 'com.github.reportportal:commons-events:f130879' - compile 'com.github.reportportal:commons-dao:c0d3d19' + compile 'com.github.reportportal:commons-dao:f71d0eb6' compile 'com.github.reportportal:commons-rules:5.3.0' compile 'com.github.reportportal:commons-model:292c8af2' compile 'com.github.reportportal:commons:7480d61' @@ -186,8 +186,6 @@ test { } } -addDockerfileToGit.dependsOn createDockerfile -beforeReleaseBuild.dependsOn addDockerfileToGit publish.dependsOn build publish.mustRunAfter build checkCommitNeeded.dependsOn removeScripts diff --git a/project-properties.gradle b/project-properties.gradle index 4761b839ae..f0fa6b3a8f 100755 --- a/project-properties.gradle +++ b/project-properties.gradle @@ -9,7 +9,7 @@ project.ext { dependencyRepos = ["commons-dao", "commons-rules", "commons-model", "commons", "commons-fonts", "plugin-api", "commons-bom"] limits = [ 'instruction': 70, - 'branch' : 53, + 'branch' : 51, 'line' : 74, 'complexity' : 60, 'method' : 65, diff --git a/src/main/java/com/epam/ta/reportportal/core/imprt/ImportLaunchHandler.java b/src/main/java/com/epam/ta/reportportal/core/imprt/ImportLaunchHandler.java index 3a2a446045..21b969be47 100644 --- a/src/main/java/com/epam/ta/reportportal/core/imprt/ImportLaunchHandler.java +++ b/src/main/java/com/epam/ta/reportportal/core/imprt/ImportLaunchHandler.java @@ -17,6 +17,7 @@ import com.epam.ta.reportportal.commons.ReportPortalUser; import com.epam.ta.reportportal.ws.model.OperationCompletionRS; +import java.util.Map; import org.springframework.web.multipart.MultipartFile; /** @@ -24,14 +25,16 @@ */ public interface ImportLaunchHandler { - /** - * Import launch from file with specified format. - * - * @param projectDetails Project Details - * @param user user - * @param format report format - * @param file file with report - * @return OperationCompletionRS - */ - OperationCompletionRS importLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, String format, MultipartFile file, String baseUrl); + /** + * Import launch from file with specified format. + * + * @param projectDetails Project Details + * @param user user + * @param format report format + * @param file file with report + * @return OperationCompletionRS + */ + OperationCompletionRS importLaunch(ReportPortalUser.ProjectDetails projectDetails, + ReportPortalUser user, String format, MultipartFile file, String baseUrl, + Map params); } \ No newline at end of file diff --git a/src/main/java/com/epam/ta/reportportal/core/imprt/ImportLaunchHandlerImpl.java b/src/main/java/com/epam/ta/reportportal/core/imprt/ImportLaunchHandlerImpl.java index d59d459129..440a203db9 100644 --- a/src/main/java/com/epam/ta/reportportal/core/imprt/ImportLaunchHandlerImpl.java +++ b/src/main/java/com/epam/ta/reportportal/core/imprt/ImportLaunchHandlerImpl.java @@ -25,6 +25,7 @@ import com.epam.ta.reportportal.exception.ReportPortalException; import com.epam.ta.reportportal.ws.model.ErrorType; import com.epam.ta.reportportal.ws.model.OperationCompletionRS; +import java.util.Map; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -53,8 +54,9 @@ public ImportLaunchHandlerImpl(ImportStrategyFactory importStrategyFactory, Mess } @Override - public OperationCompletionRS importLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, String format, - MultipartFile file, String baseUrl) { + public OperationCompletionRS importLaunch(ReportPortalUser.ProjectDetails projectDetails, + ReportPortalUser user, String format, + MultipartFile file, String baseUrl, Map params) { validate(file); @@ -68,7 +70,7 @@ public OperationCompletionRS importLaunch(ReportPortalUser.ProjectDetails projec file.getOriginalFilename() )); ImportStrategy strategy = importStrategyFactory.getImportStrategy(type, file.getOriginalFilename()); - String launchId = strategy.importLaunch(projectDetails, user, tempFile, baseUrl); + String launchId = strategy.importLaunch(projectDetails, user, tempFile, baseUrl, params); messageBus.publishActivity(new ImportFinishedEvent(user.getUserId(), user.getUsername(), projectDetails.getProjectId(), diff --git a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/AbstractImportStrategy.java b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/AbstractImportStrategy.java index b0f9b8a40b..2f1ebba0aa 100644 --- a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/AbstractImportStrategy.java +++ b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/AbstractImportStrategy.java @@ -15,6 +15,8 @@ */ package com.epam.ta.reportportal.core.imprt.impl; +import static com.epam.ta.reportportal.commons.validation.BusinessRule.expect; + import com.epam.ta.reportportal.commons.ReportPortalUser; import com.epam.ta.reportportal.core.launch.FinishLaunchHandler; import com.epam.ta.reportportal.core.launch.StartLaunchHandler; @@ -24,8 +26,13 @@ import com.epam.ta.reportportal.exception.ReportPortalException; import com.epam.ta.reportportal.ws.model.ErrorType; import com.epam.ta.reportportal.ws.model.FinishExecutionRQ; +import com.epam.ta.reportportal.ws.model.attribute.ItemAttributesRQ; import com.epam.ta.reportportal.ws.model.launch.Mode; import com.epam.ta.reportportal.ws.model.launch.StartLaunchRQ; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -43,84 +50,172 @@ */ @Component public abstract class AbstractImportStrategy implements ImportStrategy { - protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractImportStrategy.class); - private static final Date initialStartTime = new Date(0); - protected static final ExecutorService service = Executors.newFixedThreadPool(5); - - private StartLaunchHandler startLaunchHandler; - - private FinishLaunchHandler finishLaunchHandler; - - private LaunchRepository launchRepository; - - @Autowired - public void setStartLaunchHandler(StartLaunchHandler startLaunchHandler) { - this.startLaunchHandler = startLaunchHandler; - } - - @Autowired - public void setFinishLaunchHandler(FinishLaunchHandler finishLaunchHandler) { - this.finishLaunchHandler = finishLaunchHandler; - } - - @Autowired - public void setLaunchRepository(LaunchRepository launchRepository) { - this.launchRepository = launchRepository; - } - - protected ParseResults processResults(CompletableFuture... futures) { - ParseResults results = new ParseResults(); - Arrays.stream(futures).map(it -> (ParseResults) it.join()).forEach(res -> { - results.checkAndSetStartLaunchTime(res.getStartTime()); - results.increaseDuration(res.getDuration()); - }); - return results; - } - - protected String startLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, String launchName) { - StartLaunchRQ startLaunchRQ = new StartLaunchRQ(); - startLaunchRQ.setStartTime(initialStartTime); - startLaunchRQ.setName(launchName); - startLaunchRQ.setMode(Mode.DEFAULT); - return startLaunchHandler.startLaunch(user, projectDetails, startLaunchRQ).getId(); - } - - protected void finishLaunch(String launchId, ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, - ParseResults results, String baseUrl) { - FinishExecutionRQ finishExecutionRQ = new FinishExecutionRQ(); - finishExecutionRQ.setEndTime(results.getEndTime()); - finishLaunchHandler.finishLaunch(launchId, finishExecutionRQ, projectDetails, user, baseUrl); - Launch launch = launchRepository.findByUuid(launchId) - .orElseThrow(() -> new ReportPortalException(ErrorType.LAUNCH_NOT_FOUND, launchId)); - launch.setStartTime(results.getStartTime()); - launchRepository.save(launch); - } - - /** - * Got a cause exception message if it has any. - * - * @param e Exception - * @return Clean exception message - */ - protected String cleanMessage(Exception e) { - if (e.getCause() != null) { - return e.getCause().getMessage(); - } - return e.getMessage(); - } - - /* - * if the importing results do not contain initial timestamp a launch gets - * a default date if the launch is broken, time should be updated to not to broke - * the statistics - */ - protected void updateBrokenLaunch(String savedLaunchId) { - if (savedLaunchId != null) { - Launch launch = launchRepository.findByUuid(savedLaunchId) - .orElseThrow(() -> new ReportPortalException(ErrorType.LAUNCH_NOT_FOUND)); - launch.setStartTime(LocalDateTime.now()); - launch.setStatus(StatusEnum.INTERRUPTED); - launchRepository.save(launch); - } - } + + public static final String LAUNCH_NAME = "launchName"; + public static final String LAUNCH_DESCRIPTION = "description"; + public static final String ATTRIBUTE_KEY = "attributeKey"; + public static final String ATTRIBUTE_VALUE = "attributeValue"; + public static final String SKIPPED_IS_NOT_ISSUE = "skippedIsNotIssue"; + public static final String SKIPPED_ISSUE = "skippedIssue"; + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractImportStrategy.class); + private static final Date initialStartTime = new Date(0); + protected static final ExecutorService service = Executors.newFixedThreadPool(5); + public static final String LAUNCH_NAME_RESTRICTION_MSG = + "User can't import launch with the invalid number of symbols for Name."; + public static final String LAUNCH_DESCRIPTION_RESTRICTION_MSG = + "User can't import launch with the invalid number of symbols for Description."; + public static final String ATTRIBUTE_KEY_RESTRICTION_MSG = + "User can't import launch with the invalid number of symbols for Attribute Key."; + public static final String ATTRIBUTE_KEY_WITHOUT_VALUE_MSG = + "User can't import launch with only Attribute Key without Attribute Value."; + public static final String ATTRIBUTE_VALUE_RESTRICTION_MSG = + "User can't import launch with the invalid number of symbols for Attribute Value."; + public static final String INCORRECT_NOT_ISSUE_PARAMETER_MSG = + "User can't import launch with invalid value for parameter skippedIsNotIssue."; + public static final int MAX_ATTRIBUTE_LENGTH = 512; + public static final int MAX_DESCRIPTION_LENGTH = 2048; + public static final int MAX_NAME_LENGTH = 256; + + private StartLaunchHandler startLaunchHandler; + + private FinishLaunchHandler finishLaunchHandler; + + private LaunchRepository launchRepository; + + @Autowired + public void setStartLaunchHandler(StartLaunchHandler startLaunchHandler) { + this.startLaunchHandler = startLaunchHandler; + } + + @Autowired + public void setFinishLaunchHandler(FinishLaunchHandler finishLaunchHandler) { + this.finishLaunchHandler = finishLaunchHandler; + } + + @Autowired + public void setLaunchRepository(LaunchRepository launchRepository) { + this.launchRepository = launchRepository; + } + + protected ParseResults processResults(CompletableFuture... futures) { + ParseResults results = new ParseResults(); + Arrays.stream(futures).map(it -> (ParseResults) it.join()).forEach(res -> { + results.checkAndSetStartLaunchTime(res.getStartTime()); + results.increaseDuration(res.getDuration()); + }); + return results; + } + + protected String startLaunch(ReportPortalUser.ProjectDetails projectDetails, + ReportPortalUser user, String launchName, Map params) { + StartLaunchRQ startLaunchRQ = new StartLaunchRQ(); + startLaunchRQ.setStartTime(initialStartTime); + startLaunchRQ.setName(params.get(LAUNCH_NAME) != null ? params.get(LAUNCH_NAME) : launchName); + startLaunchRQ.setDescription(params.get(LAUNCH_DESCRIPTION)); + startLaunchRQ.setMode(Mode.DEFAULT); + Set itemAttributes = getItemAttributes(params); + startLaunchRQ.setAttributes(itemAttributes); + return startLaunchHandler.startLaunch(user, projectDetails, startLaunchRQ).getId(); + } + + private Set getItemAttributes(Map params) { + Set itemAttributes = new HashSet<>(); + if (params.get(ATTRIBUTE_VALUE) != null) { + itemAttributes.add( + new ItemAttributesRQ(params.get(ATTRIBUTE_KEY), params.get(ATTRIBUTE_VALUE))); + } + if (params.get(SKIPPED_IS_NOT_ISSUE) != null && Boolean.parseBoolean(params.get( + SKIPPED_IS_NOT_ISSUE))) { + itemAttributes.add(new ItemAttributesRQ(SKIPPED_ISSUE, "true", true)); + } + return itemAttributes; + } + + protected void finishLaunch(String launchId, ReportPortalUser.ProjectDetails projectDetails, + ReportPortalUser user, + ParseResults results, String baseUrl) { + FinishExecutionRQ finishExecutionRQ = new FinishExecutionRQ(); + finishExecutionRQ.setEndTime(results.getEndTime()); + finishLaunchHandler.finishLaunch(launchId, finishExecutionRQ, projectDetails, user, baseUrl); + Launch launch = launchRepository.findByUuid(launchId) + .orElseThrow(() -> new ReportPortalException(ErrorType.LAUNCH_NOT_FOUND, launchId)); + launch.setStartTime(results.getStartTime()); + launchRepository.save(launch); + } + + /** + * Got a cause exception message if it has any. + * + * @param e Exception + * @return Clean exception message + */ + protected String cleanMessage(Exception e) { + if (e.getCause() != null) { + return e.getCause().getMessage(); + } + return e.getMessage(); + } + + /* + * if the importing results do not contain initial timestamp a launch gets + * a default date if the launch is broken, time should be updated to not to broke + * the statistics + */ + protected void updateBrokenLaunch(String savedLaunchId) { + if (savedLaunchId != null) { + Launch launch = launchRepository.findByUuid(savedLaunchId) + .orElseThrow(() -> new ReportPortalException(ErrorType.LAUNCH_NOT_FOUND)); + launch.setStartTime(LocalDateTime.now()); + launch.setStatus(StatusEnum.INTERRUPTED); + launchRepository.save(launch); + } + } + + protected void validateOverrideParameters(Map params) { + validateLaunchName(params); + validateLaunchDescription(params); + validateAttributeKey(params); + validateAttributeKeyWithValue(params); + validateAttributeValue(params); + validateSkippedParameter(params); + } + + private void validateLaunchName(Map params) { + String launchName = params.get(LAUNCH_NAME); + boolean isValid = launchName == null || (1 < launchName.length() && launchName.length() <= MAX_NAME_LENGTH); + expect(isValid, Predicate.isEqual(true)).verify(ErrorType.BAD_REQUEST_ERROR, LAUNCH_NAME_RESTRICTION_MSG); + } + + private void validateLaunchDescription(Map params) { + String launchDescription = params.get(LAUNCH_DESCRIPTION); + boolean isValid = launchDescription == null || (launchDescription.length() <= MAX_DESCRIPTION_LENGTH); + expect(isValid, Predicate.isEqual(true)).verify(ErrorType.BAD_REQUEST_ERROR, LAUNCH_DESCRIPTION_RESTRICTION_MSG); + } + + private void validateAttributeKey(Map params) { + String attributeKey = params.get(ATTRIBUTE_KEY); + boolean isValid = attributeKey == null || (attributeKey.length() <= MAX_ATTRIBUTE_LENGTH); + expect(isValid, Predicate.isEqual(true)).verify(ErrorType.BAD_REQUEST_ERROR, ATTRIBUTE_KEY_RESTRICTION_MSG); + } + + private void validateAttributeKeyWithValue(Map params) { + String attributeKey = params.get(ATTRIBUTE_KEY); + String attributeValue = params.get(ATTRIBUTE_VALUE); + boolean isValid = attributeKey == null || attributeValue != null; + expect(isValid, Predicate.isEqual(true)).verify(ErrorType.BAD_REQUEST_ERROR, ATTRIBUTE_KEY_WITHOUT_VALUE_MSG); + } + + private void validateAttributeValue(Map params) { + String attributeValue = params.get(ATTRIBUTE_VALUE); + boolean isValid = attributeValue == null || (attributeValue.length() <= MAX_ATTRIBUTE_LENGTH); + expect(isValid, Predicate.isEqual(true)).verify(ErrorType.BAD_REQUEST_ERROR, ATTRIBUTE_VALUE_RESTRICTION_MSG); + } + + private void validateSkippedParameter(Map params) { + String notIssue = params.get(SKIPPED_IS_NOT_ISSUE); + boolean isValid = + notIssue == null || "true".equalsIgnoreCase(notIssue) || "false".equalsIgnoreCase(notIssue); + expect(isValid, Predicate.isEqual(true)).verify(ErrorType.BAD_REQUEST_ERROR, + INCORRECT_NOT_ISSUE_PARAMETER_MSG); + } } diff --git a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/ImportStrategy.java b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/ImportStrategy.java index 973f83a336..7038eef804 100644 --- a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/ImportStrategy.java +++ b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/ImportStrategy.java @@ -18,6 +18,7 @@ import com.epam.ta.reportportal.commons.ReportPortalUser; import java.io.File; +import java.util.Map; /** * Handler for processing launch importing. @@ -33,5 +34,6 @@ public interface ImportStrategy { * @param file zip file that contains xml test reports * @return launch uuid */ - String importLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, File file, String baseUrl); + String importLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, + File file, String baseUrl, Map params); } diff --git a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/XmlImportStrategy.java b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/XmlImportStrategy.java index cf61366ec5..2ce5219297 100644 --- a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/XmlImportStrategy.java +++ b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/XmlImportStrategy.java @@ -19,6 +19,7 @@ import com.epam.ta.reportportal.core.imprt.impl.junit.XunitParseJob; import com.epam.ta.reportportal.exception.ReportPortalException; import com.epam.ta.reportportal.ws.model.ErrorType; +import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -36,35 +37,42 @@ @Service public class XmlImportStrategy extends AbstractImportStrategy { - @Autowired - private Provider xmlParseJobProvider; + @Autowired + private Provider xmlParseJobProvider; - @Override - public String importLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, File file, String baseUrl) { - try { - return processXmlFile(file, projectDetails, user, baseUrl); - } finally { - try { - ofNullable(file).ifPresent(File::delete); - } catch (Exception e) { - LOGGER.error("File '{}' was not successfully deleted.", file.getName(), e); - } - } - } + @Override + public String importLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, + File file, String baseUrl, Map params) { + validateOverrideParameters(params); + try { + return processXmlFile(file, projectDetails, user, baseUrl, params); + } finally { + try { + ofNullable(file).ifPresent(File::delete); + } catch (Exception e) { + LOGGER.error("File '{}' was not successfully deleted.", file.getName(), e); + } + } + } - private String processXmlFile(File xml, ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, String baseUrl) { - //copy of the launch's id to use it in catch block if something goes wrong - String savedLaunchId = null; - try (InputStream xmlStream = new FileInputStream(xml)) { - String launchId = startLaunch(projectDetails, user, xml.getName().substring(0, xml.getName().indexOf("." + XML_EXTENSION))); - savedLaunchId = launchId; - XunitParseJob job = xmlParseJobProvider.get().withParameters(projectDetails, launchId, user, xmlStream); - ParseResults parseResults = job.call(); - finishLaunch(launchId, projectDetails, user, parseResults, baseUrl); - return launchId; - } catch (Exception e) { - updateBrokenLaunch(savedLaunchId); - throw new ReportPortalException(ErrorType.IMPORT_FILE_ERROR, cleanMessage(e)); - } - } + private String processXmlFile(File xml, ReportPortalUser.ProjectDetails projectDetails, + ReportPortalUser user, String baseUrl, Map params) { + //copy of the launch's id to use it in catch block if something goes wrong + String savedLaunchId = null; + try (InputStream xmlStream = new FileInputStream(xml)) { + String launchId = startLaunch(projectDetails, user, + xml.getName().substring(0, xml.getName().indexOf("." + XML_EXTENSION)), params); + savedLaunchId = launchId; + XunitParseJob job = xmlParseJobProvider.get() + .withParameters(projectDetails, launchId, user, xmlStream, + params.get(SKIPPED_IS_NOT_ISSUE) != null && Boolean.parseBoolean(params.get( + SKIPPED_IS_NOT_ISSUE))); + ParseResults parseResults = job.call(); + finishLaunch(launchId, projectDetails, user, parseResults, baseUrl); + return launchId; + } catch (Exception e) { + updateBrokenLaunch(savedLaunchId); + throw new ReportPortalException(ErrorType.IMPORT_FILE_ERROR, cleanMessage(e)); + } + } } diff --git a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/ZipImportStrategy.java b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/ZipImportStrategy.java index 02aa156cc8..d9cbbb04fe 100644 --- a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/ZipImportStrategy.java +++ b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/ZipImportStrategy.java @@ -19,6 +19,7 @@ import com.epam.ta.reportportal.core.imprt.impl.junit.XunitParseJob; import com.epam.ta.reportportal.exception.ReportPortalException; import com.epam.ta.reportportal.ws.model.ErrorType; +import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -40,50 +41,56 @@ */ @Service public class ZipImportStrategy extends AbstractImportStrategy { - private static final Predicate isFile = zipEntry -> !zipEntry.isDirectory(); - private static final Predicate isXml = zipEntry -> zipEntry.getName().endsWith(XML_EXTENSION); + private static final Predicate isFile = zipEntry -> !zipEntry.isDirectory(); + private static final Predicate isXml = zipEntry -> zipEntry.getName().endsWith(XML_EXTENSION); - @Autowired - private Provider xmlParseJobProvider; + @Autowired + private Provider xmlParseJobProvider; - @Override - public String importLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, File file, String baseUrl) { - try { - return processZipFile(file, projectDetails, user, baseUrl); - } finally { - try { - ofNullable(file).ifPresent(File::delete); - } catch (Exception e) { - LOGGER.error("File '{}' was not successfully deleted.", file.getName(), e); - } - } - } + @Override + public String importLaunch(ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, + File file, String baseUrl, Map params) { + validateOverrideParameters(params); + try { + return processZipFile(file, projectDetails, user, baseUrl, params); + } finally { + try { + ofNullable(file).ifPresent(File::delete); + } catch (Exception e) { + LOGGER.error("File '{}' was not successfully deleted.", file.getName(), e); + } + } + } - private String processZipFile(File zip, ReportPortalUser.ProjectDetails projectDetails, ReportPortalUser user, String baseUrl) { - //copy of the launch's id to use it in catch block if something goes wrong - String savedLaunchId = null; - try (ZipFile zipFile = new ZipFile(zip)) { - String launchId = startLaunch(projectDetails, user, zip.getName().substring(0, zip.getName().indexOf("." + ZIP_EXTENSION))); - savedLaunchId = launchId; - CompletableFuture[] futures = zipFile.stream().filter(isFile.and(isXml)).map(zipEntry -> { - XunitParseJob job = xmlParseJobProvider.get() - .withParameters(projectDetails, launchId, user, getEntryStream(zipFile, zipEntry)); - return CompletableFuture.supplyAsync(job::call, service); - }).toArray(CompletableFuture[]::new); - ParseResults parseResults = processResults(futures); - finishLaunch(launchId, projectDetails, user, parseResults, baseUrl); - return launchId; - } catch (Exception e) { - updateBrokenLaunch(savedLaunchId); - throw new ReportPortalException(ErrorType.IMPORT_FILE_ERROR, cleanMessage(e)); - } - } + private String processZipFile(File zip, ReportPortalUser.ProjectDetails projectDetails, + ReportPortalUser user, String baseUrl, Map params) { + //copy of the launch's id to use it in catch block if something goes wrong + String savedLaunchId = null; + try (ZipFile zipFile = new ZipFile(zip)) { + String launchId = startLaunch(projectDetails, user, + zip.getName().substring(0, zip.getName().indexOf("." + ZIP_EXTENSION)), params); + savedLaunchId = launchId; + CompletableFuture[] futures = zipFile.stream().filter(isFile.and(isXml)).map(zipEntry -> { + XunitParseJob job = xmlParseJobProvider.get() + .withParameters(projectDetails, launchId, user, getEntryStream(zipFile, zipEntry), + params.get(SKIPPED_IS_NOT_ISSUE) != null && Boolean.parseBoolean(params.get( + SKIPPED_IS_NOT_ISSUE))); + return CompletableFuture.supplyAsync(job::call, service); + }).toArray(CompletableFuture[]::new); + ParseResults parseResults = processResults(futures); + finishLaunch(launchId, projectDetails, user, parseResults, baseUrl); + return launchId; + } catch (Exception e) { + updateBrokenLaunch(savedLaunchId); + throw new ReportPortalException(ErrorType.IMPORT_FILE_ERROR, cleanMessage(e)); + } + } - private InputStream getEntryStream(ZipFile file, ZipEntry zipEntry) { - try { - return file.getInputStream(zipEntry); - } catch (IOException e) { - throw new ReportPortalException(ErrorType.IMPORT_FILE_ERROR, e.getMessage()); - } - } + private InputStream getEntryStream(ZipFile file, ZipEntry zipEntry) { + try { + return file.getInputStream(zipEntry); + } catch (IOException e) { + throw new ReportPortalException(ErrorType.IMPORT_FILE_ERROR, e.getMessage()); + } + } } diff --git a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/junit/XunitImportHandler.java b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/junit/XunitImportHandler.java index 4ec2067457..99b16f7566 100644 --- a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/junit/XunitImportHandler.java +++ b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/junit/XunitImportHandler.java @@ -25,6 +25,7 @@ import com.epam.ta.reportportal.entity.enums.TestItemTypeEnum; import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; import com.epam.ta.reportportal.ws.model.StartTestItemRQ; +import com.epam.ta.reportportal.ws.model.issue.Issue; import com.epam.ta.reportportal.ws.model.log.SaveLogRQ; import com.google.common.base.Strings; import org.apache.commons.lang3.StringUtils; @@ -48,6 +49,7 @@ import java.util.Optional; import static com.epam.ta.reportportal.core.imprt.impl.DateUtils.toMillis; +import static com.epam.ta.reportportal.entity.enums.TestItemIssueGroup.NOT_ISSUE_FLAG; @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @@ -75,6 +77,7 @@ public XunitImportHandler(StartTestItemHandler startTestItemHandler, FinishTestI private ReportPortalUser.ProjectDetails projectDetails; private ReportPortalUser user; private String launchUuid; + private boolean skippedIsNotIssue = false; //need to know item's id to attach System.out/System.err logs private String currentItemUuid; @@ -234,6 +237,7 @@ private void startStepItem(String name, String startTime, String duration) { private void finishRootItem() { FinishTestItemRQ rq = new FinishTestItemRQ(); + markAsNotIssue(rq); rq.setEndTime(EntityUtils.TO_DATE.apply(startItemTime)); finishTestItemHandler.finishTestItem(user, projectDetails, itemUuids.poll(), rq); status = null; @@ -241,6 +245,7 @@ private void finishRootItem() { private void finishTestItem() { FinishTestItemRQ rq = new FinishTestItemRQ(); + markAsNotIssue(rq); startItemTime = startItemTime.plus(currentDuration, ChronoUnit.MILLIS); commonDuration += currentDuration; rq.setEndTime(EntityUtils.TO_DATE.apply(startItemTime)); @@ -250,6 +255,14 @@ private void finishTestItem() { status = null; } + private void markAsNotIssue(FinishTestItemRQ rq) { + if (StatusEnum.SKIPPED.equals(status) && skippedIsNotIssue) { + Issue issue = new Issue(); + issue.setIssueType(NOT_ISSUE_FLAG.getValue()); + rq.setIssue(issue); + } + } + private void attachLog(LogLevel logLevel) { if (null != message && message.length() != 0) { SaveLogRQ saveLogRQ = new SaveLogRQ(); @@ -261,10 +274,12 @@ private void attachLog(LogLevel logLevel) { } } - XunitImportHandler withParameters(ReportPortalUser.ProjectDetails projectDetails, String launchId, ReportPortalUser user) { + XunitImportHandler withParameters(ReportPortalUser.ProjectDetails projectDetails, String launchId, + ReportPortalUser user, boolean skipped) { this.projectDetails = projectDetails; this.launchUuid = launchId; this.user = user; + this.skippedIsNotIssue = skipped; return this; } diff --git a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/junit/XunitParseJob.java b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/junit/XunitParseJob.java index b8f7421bcd..82f1e94c18 100644 --- a/src/main/java/com/epam/ta/reportportal/core/imprt/impl/junit/XunitParseJob.java +++ b/src/main/java/com/epam/ta/reportportal/core/imprt/impl/junit/XunitParseJob.java @@ -73,9 +73,9 @@ public ParseResults call() { } public XunitParseJob withParameters(ReportPortalUser.ProjectDetails projectDetails, String launchId, ReportPortalUser user, - InputStream xmlInputStream) { + InputStream xmlInputStream, boolean skipped) { this.xmlInputStream = xmlInputStream; - this.handler = handler.withParameters(projectDetails, launchId, user); + this.handler = handler.withParameters(projectDetails, launchId, user, skipped); return this; } diff --git a/src/main/java/com/epam/ta/reportportal/ws/controller/LaunchController.java b/src/main/java/com/epam/ta/reportportal/ws/controller/LaunchController.java index c1be158566..705bdfbd2d 100644 --- a/src/main/java/com/epam/ta/reportportal/ws/controller/LaunchController.java +++ b/src/main/java/com/epam/ta/reportportal/ws/controller/LaunchController.java @@ -32,6 +32,8 @@ import com.epam.ta.reportportal.ws.resolver.FilterFor; import com.epam.ta.reportportal.ws.resolver.SortFor; import com.google.common.net.HttpHeaders; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; @@ -369,16 +371,50 @@ public DeleteBulkRS deleteLaunches(@PathVariable String projectName, @RequestBod return deleteLaunchMessageHandler.deleteLaunches(deleteBulkRQ, projectExtractor.extractProjectDetails(user, normalizeId(projectName)), user); } - @PostMapping(value = "/import", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }) - @ResponseStatus(OK) - @ApiOperation(value = "Import junit xml report", notes = "Only following formats are supported: zip and xml.") - public OperationCompletionRS importLaunch(@PathVariable String projectName, @RequestParam("file") MultipartFile file, - @AuthenticationPrincipal ReportPortalUser user, HttpServletRequest request) { - return importLaunchHandler.importLaunch(projectExtractor.extractProjectDetails(user, normalizeId(projectName)), - user, - "XUNIT", - file, - composeBaseUrl(request) - ); - } + @ApiImplicitParams({ + @ApiImplicitParam( + name = "launchName", + dataType = "string", + paramType = "query", + value = "Override Launch Name" + ), + @ApiImplicitParam( + name = "description", + dataType = "string", + paramType = "query", + value = "Override Launch Description" + ), + @ApiImplicitParam( + name = "attributeKey", + dataType = "string", + paramType = "query", + value = "Add Launch attribute key" + ), + @ApiImplicitParam( + name = "attributeValue", + dataType = "string", + paramType = "query", + value = "Add Launch attribute value" + ), + @ApiImplicitParam( + name = "skippedIsNotIssue", + dataType = "boolean", + paramType = "query", + value = "true: no defect type is applied to skipped issue" + ) + }) + @PostMapping(value = "/import", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }) + @ResponseStatus(OK) + @ApiOperation(value = "Import junit xml report", notes = "Only following formats are supported: zip and xml.") + public OperationCompletionRS importLaunch(@PathVariable String projectName, @RequestParam("file") MultipartFile file, + @AuthenticationPrincipal ReportPortalUser user, HttpServletRequest request, + @ApiParam(required = false) @RequestParam Map params) { + return importLaunchHandler.importLaunch(projectExtractor.extractProjectDetails(user, normalizeId(projectName)), + user, + "XUNIT", + file, + composeBaseUrl(request), + params + ); + } }