mirror of
https://github.com/kochetkov-ma/allure-server.git
synced 2024-11-21 16:46:43 +02:00
Feature/tms (#89)
* add: delete old format support add: fix build add: new plugin system add: spring boot 3, update allure, update all libs version, update vaadin, update gradle, update nodejs * add: fix test * add: fix test * add: fix test * add: fix test * add: fix docker image platforms * add: fix docker image platforms * add: enabled/disabled plugins
This commit is contained in:
parent
650449cc83
commit
0d7342b9f2
@ -1,7 +1,6 @@
|
||||
node_modules/
|
||||
.helm/
|
||||
.github/
|
||||
build/
|
||||
.idea/
|
||||
.gradle/
|
||||
tmp/
|
||||
tmp/
|
||||
|
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@ -0,0 +1,19 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 200
|
||||
|
||||
[*.java]
|
||||
max_line_length = 180
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
18
.github/workflows/check.yml
vendored
18
.github/workflows/check.yml
vendored
@ -3,7 +3,8 @@ name: Build / Test / Check
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
NODE_VERSION: 12.x
|
||||
NODE_VERSION: 20.13.1
|
||||
GRADLE_VERSION: 8.8
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -11,19 +12,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fast checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
java-package: jdk
|
||||
architecture: x64
|
||||
java-version: '21'
|
||||
distribution: 'corretto'
|
||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: Build with Gradle
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
uses: eskatos/gradle-command-action@v3
|
||||
with:
|
||||
gradle-version: 6.9.2
|
||||
gradle-version: ${{ env.GRADLE_VERSION }}
|
||||
arguments: '--stacktrace --info build'
|
||||
|
51
.github/workflows/release.yml
vendored
51
.github/workflows/release.yml
vendored
@ -9,36 +9,50 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
## Build prepare ##
|
||||
- name: Fast checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Set RELEASE_VERSION
|
||||
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF:11}
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
java-package: jdk
|
||||
architecture: x64
|
||||
java-version: '21'
|
||||
distribution: 'corretto'
|
||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
## Build Image ##
|
||||
- name: Build with Gradle
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
uses: eskatos/gradle-command-action@v3
|
||||
with:
|
||||
gradle-version: 6.9.2
|
||||
gradle-version: ${{ env.GRADLE_VERSION }}
|
||||
arguments: ' -Pversion=${{ env.RELEASE_VERSION }} --stacktrace bootJar'
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
- name: Archive code coverage results
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: allure-server-${{ env.RELEASE_VERSION }}.jar
|
||||
path: build/libs/allure-server-${{ env.RELEASE_VERSION }}.jar
|
||||
|
||||
## Release in DockerHub ##
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Publish to Registry
|
||||
uses: elgohr/Publish-Docker-Github-Action@v5
|
||||
env:
|
||||
RELEASE_VERSION: ${{ env.RELEASE_VERSION }}
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
name: kochetkovma/allure-server
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
tags: "latest,${{ env.RELEASE_VERSION }}"
|
||||
buildargs: RELEASE_VERSION
|
||||
|
||||
## Release in GitHub ##
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@latest
|
||||
@ -64,14 +78,3 @@ jobs:
|
||||
asset_path: build/libs/allure-server-${{ env.RELEASE_VERSION }}.jar
|
||||
asset_name: allure-server.jar
|
||||
asset_content_type: application/jar
|
||||
- name: Publish to Registry
|
||||
uses: elgohr/Publish-Docker-Github-Action@v5
|
||||
env:
|
||||
RELEASE_VERSION: ${{ env.RELEASE_VERSION }}
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
with:
|
||||
name: kochetkovma/allure-server
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
tags: "latest,${{ env.RELEASE_VERSION }}"
|
||||
buildargs: RELEASE_VERSION
|
||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
/.gradle
|
||||
/.idea
|
||||
**/wrapper/gradle-wrapper.jar
|
||||
/build
|
||||
/keys
|
||||
/allure
|
||||
@ -14,7 +13,6 @@ webpack.config.js
|
||||
webpack.generated.js
|
||||
|
||||
# Gradle
|
||||
gradlew
|
||||
gradlew.bat
|
||||
|
||||
# Compiled class file
|
||||
@ -30,7 +28,6 @@ gradlew.bat
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
@ -52,4 +49,8 @@ allure-server-store/
|
||||
tmp/
|
||||
|
||||
pg-secret.yaml
|
||||
CA.pem
|
||||
CA.pem
|
||||
|
||||
**/generated/**
|
||||
*generated*
|
||||
*-private*
|
||||
|
@ -4,7 +4,7 @@ image:
|
||||
repository: kochetkovma/allure-server
|
||||
pullPolicy: IfNotPresent
|
||||
## Don't use 'latest' ;)
|
||||
tag: 2.12.0
|
||||
tag: 2.13.4
|
||||
|
||||
## Add 'key: value collection' and delete '{ }' if need
|
||||
## Uncomment and remove '{ }' if need
|
||||
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* NOTICE: this is an auto-generated file
|
||||
*
|
||||
* This file has been generated for `pnpm install` task.
|
||||
* It is used to pin client side dependencies.
|
||||
* This file will be overwritten on every run.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const versionsFile = require('path').resolve(__dirname, 'build/frontend/versions.json');
|
||||
|
||||
if (!fs.existsSync(versionsFile)) {
|
||||
return;
|
||||
}
|
||||
const versions = JSON.parse(fs.readFileSync(versionsFile, 'utf-8'));
|
||||
|
||||
module.exports = {
|
||||
hooks: {
|
||||
readPackage
|
||||
}
|
||||
};
|
||||
|
||||
function readPackage(pkg) {
|
||||
const { dependencies } = pkg;
|
||||
|
||||
if (dependencies) {
|
||||
for (let k in versions) {
|
||||
if (dependencies[k] && dependencies[k] !== versions[k]) {
|
||||
pkg.dependencies[k] = versions[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forcing chokidar version for now until new babel version is available
|
||||
// check out https://github.com/babel/babel/issues/11488
|
||||
if (pkg.dependencies.chokidar) {
|
||||
pkg.dependencies.chokidar = '^3.4.0';
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
11
Dockerfile
11
Dockerfile
@ -1,12 +1,7 @@
|
||||
FROM gradle:6.9.2-jdk11 as build
|
||||
COPY . .
|
||||
ARG RELEASE_VERSION=${RELEASE_VERSION:-0.0.0}
|
||||
RUN gradle -Pversion=docker -i -s --no-daemon bootJar
|
||||
|
||||
FROM openjdk:11.0.15-jre-slim-bullseye as production
|
||||
COPY --from=build /home/gradle/build/libs/allure-server-docker.jar /allure-server-docker.jar
|
||||
FROM amazoncorretto:21-alpine
|
||||
COPY build/libs/*.jar /allure-server-docker.jar
|
||||
# Set port
|
||||
EXPOSE ${PORT:-8080}
|
||||
# Run application
|
||||
ENV JAVA_OPTS="-Xms256m -Xmx2048m"
|
||||
ENTRYPOINT ["java", "-Dloader.path=/ext", "-cp", "allure-server-docker.jar", "-Dspring.profiles.active=${SPRING_PROFILES_ACTIVE:default}", "org.springframework.boot.loader.PropertiesLauncher"]
|
||||
ENTRYPOINT ["java", "-Dloader.path=/ext", "-jar", "allure-server-docker.jar", "-Dspring.profiles.active=${SPRING_PROFILES_ACTIVE:default}"]
|
||||
|
14
README.md
14
README.md
@ -2,16 +2,18 @@ Allure Portal (Allure Report Server)
|
||||
=================================
|
||||
![Build / Test / Check](https://github.com/kochetkov-ma/allure-server/workflows/Build%20/%20Test%20/%20Check/badge.svg?branch=master)
|
||||
|
||||
[![jdk11](https://camo.githubusercontent.com/f3886a668d85acf93f6fec0beadcbb40a5446014/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6a646b2d31312d7265642e737667)](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html)
|
||||
[![gradle](https://camo.githubusercontent.com/f7b6b0146f2ee4c36d3da9fa18d709301d91f811/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f746f6f6c2d677261646c652d626c75652e737667)](https://gradle.org/)
|
||||
[![junit](https://camo.githubusercontent.com/d2ba89c41121d7c6223c1ad926380235cf95ef82/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6a756e69742d706c6174666f726d2d627269676874677265656e2e737667)](https://github.com/junit-team/junit4/blob/master/doc/ReleaseNotes4.13.md)
|
||||
![Static Badge](https://img.shields.io/badge/java-21-brightgreen)
|
||||
![Static Badge](https://img.shields.io/badge/gradle-8.8-brightgreen)
|
||||
|
||||
![Docker Image Version](https://img.shields.io/docker/v/kochetkovma/allure-server?label=DockerHub)
|
||||
![Docker Pulls](https://img.shields.io/docker/pulls/kochetkovma/allure-server?link=https)
|
||||
|
||||
|
||||
[![checkstyle](https://img.shields.io/badge/checkstyle-google-blue)](https://github.com/checkstyle/checkstyle)
|
||||
[![pmd](https://img.shields.io/badge/pmd-passed-green)](https://github.com/pmd/pmd)
|
||||
[![spotbugs](https://img.shields.io/badge/spotbugs-passed-green)](https://github.com/spotbugs/spotbugs)
|
||||
|
||||
## About
|
||||
|
||||
https://allurereport.org/docs
|
||||
|
||||
Allure server for store / aggregate / manage Allure results and generate / manage Allure Reports.
|
||||
|
||||
There is simple API with Swagger(OpenAPI) Description.
|
||||
|
112
build.gradle
112
build.gradle
@ -1,26 +1,22 @@
|
||||
import java.util.regex.Pattern
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'jacoco'
|
||||
id 'idea'
|
||||
id 'pmd'
|
||||
id 'checkstyle'
|
||||
|
||||
id 'com.github.spotbugs' version '4.6.0'
|
||||
id 'io.freefair.lombok' version '5.3.3.3'
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'io.freefair.lombok' version '8.6'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
|
||||
// https://docs.spring.io/spring-boot/docs/current/reference/html/dependency-versions.html
|
||||
id 'org.springframework.boot' version '2.7.1'
|
||||
id 'io.spring.dependency-management' version '1.0.12.RELEASE'
|
||||
id 'org.springframework.boot' version '3.3.1'
|
||||
id 'io.spring.dependency-management' version '1.1.5'
|
||||
|
||||
id "com.vaadin" version "23.1.3"
|
||||
id "com.vaadin" version "24.4.4"
|
||||
id "org.openapi.generator" version '7.6.0'
|
||||
}
|
||||
apply from: './gradle/dependencies.gradle'
|
||||
apply from: './gradle/checking.gradle'
|
||||
apply from: './gradle/testing.gradle'
|
||||
|
||||
generateLombokConfig.enabled = false
|
||||
|
||||
group = theGroup
|
||||
archivesBaseName = theArchivesBaseName
|
||||
idea {
|
||||
@ -33,20 +29,22 @@ compileJava {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
wrapper {
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
gradleVersion = '6.9.2'
|
||||
gradleVersion = '8.8'
|
||||
doLast {
|
||||
delete "$projectDir/gradlew.bat", "$projectDir/gradlew"
|
||||
}
|
||||
}
|
||||
vaadin {
|
||||
nodeVersion = 'v16.15.0'
|
||||
pnpmEnable = false
|
||||
nodeAutoUpdate = false
|
||||
nodeVersion = 'v20.13.1'
|
||||
pnpmEnable = true
|
||||
productionMode = true
|
||||
forceProductionBuild
|
||||
}
|
||||
classes {
|
||||
doLast {
|
||||
@ -54,6 +52,84 @@ classes {
|
||||
def releaseVersion = System.env.RELEASE_VERSION as String
|
||||
if (releaseVersion) {
|
||||
new File(resourcesDir, "version.info").text = releaseVersion
|
||||
} else {
|
||||
new File(resourcesDir, "version.info").text = version
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
springBoot {
|
||||
mainClass = "ru.iopump.qa.allure.Application"
|
||||
}
|
||||
|
||||
tasks.named("bootJar") {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'org.springframework.boot.loader.launch.PropertiesLauncher'
|
||||
}
|
||||
}
|
||||
|
||||
//// OPENAPI ////
|
||||
|
||||
// https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-gradle-plugin
|
||||
// https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators.md
|
||||
// https://openapi-generator.tech/docs/generators/spring/
|
||||
openApiGenerate {
|
||||
generatorName = "spring"
|
||||
library = "spring-boot"
|
||||
|
||||
inputSpec = "$rootDir/src/test/resources/tms/openapi-youtrack.json"
|
||||
outputDir = "$projectDir/build/generated"
|
||||
|
||||
apiPackage = "org.brewcode.api.youtrack"
|
||||
invokerPackage = "org.brewcode.api.youtrack.invoker"
|
||||
modelPackage = "org.brewcode.api.youtrack.model"
|
||||
modelNameSuffix = "Dto"
|
||||
|
||||
importMappings = [
|
||||
SavedQueryDto: "org.brewcode.api.youtrack.model.SavedQueryDto",
|
||||
]
|
||||
|
||||
configOptions = [
|
||||
useBeanValidation : "false",
|
||||
useJakartaEe : "true",
|
||||
serializationLibrary : "jackson",
|
||||
annotationLibrary : "swagger2",
|
||||
generatedConstructorWithRequiredArgs: "true",
|
||||
dateLibrary : "java8",
|
||||
useSpringBoot3 : "true",
|
||||
interfaceOnly : "true",
|
||||
openApiNullable : "false",
|
||||
useResponseEntity : "false", // Не использовать ResponseEntity<Е>, а сразу вернуть Е
|
||||
skipDefaultInterface : "true" // Не добавлять в интерфейс default реализацию
|
||||
]
|
||||
}
|
||||
|
||||
tasks.named("openApiGenerate") {
|
||||
doLast {
|
||||
def directory = file("build/generated/src/main/java/org/brewcode/api/youtrack/model")
|
||||
directory.eachFile {
|
||||
def pattern = Pattern.compile('Type\\(value = (.+).class')
|
||||
def matcher = pattern.matcher(it.text)
|
||||
if (matcher.find())
|
||||
it.text = matcher.replaceAll { match -> 'Type(value = org.brewcode.api.youtrack.model.%s.class'.formatted(match.group(1)) }
|
||||
|
||||
if (it.name == 'BaseBundleDto.java') {
|
||||
it.text = it.text.readLines().withIndex().findAll { line, index -> index < 72 || index > 98 }.collect { it[0] }.join("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileJava.dependsOn tasks.openApiGenerate
|
||||
compileTestJava.dependsOn tasks.openApiGenerate
|
||||
sourceSets.main.java.srcDirs += tasks.openApiGenerate
|
||||
|
||||
bootJar {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'org.springframework.boot.loader.launch.PropertiesLauncher'
|
||||
}
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '8.8'
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ services:
|
||||
allure-server:
|
||||
# For local debug #
|
||||
# build: .
|
||||
image: kochetkovma/allure-server:2.12.0
|
||||
image: kochetkovma/allure-server:2.13.5
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./tmp/allure:/allure/:rw
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: oauth
|
||||
# BASIC_AUTH_ENABLE: true
|
||||
# BASIC_AUTH_ENABLE: true
|
||||
|
@ -3,7 +3,7 @@ services:
|
||||
allure-server:
|
||||
# For local debug #
|
||||
# build: .
|
||||
image: kochetkovma/allure-server:2.12.0
|
||||
image: kochetkovma/allure-server:2.13.5
|
||||
environment:
|
||||
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/allure
|
||||
SPRING_DATASOURCE_USERNAME: postgres
|
||||
@ -26,4 +26,4 @@ services:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: allure
|
||||
ports:
|
||||
- 5432:5432
|
||||
- 5432:5432
|
||||
|
@ -1,25 +0,0 @@
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/iron-iconset-svg/iron-iconset-svg.js';
|
||||
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
const template = html`<iron-iconset-svg name="icomoon" size="1024">
|
||||
<svg>
|
||||
<defs>
|
||||
<g id="linkedin">
|
||||
<path fill="#0077b5" style="fill: var(--color1, #0077b5)" class="path1"
|
||||
d="M872.405 872.619h-151.637v-237.611c0-56.661-1.152-129.579-79.019-129.579-79.061 0-91.136 61.653-91.136 125.397v241.792h-151.637v-488.619h145.664v66.603h1.963c20.352-38.4 69.845-78.933 143.787-78.933 153.643 0 182.059 101.12 182.059 232.747zM227.712 317.141c-48.811 0-88.021-39.509-88.021-88.107 0-48.555 39.253-88.021 88.021-88.021 48.64 0 88.064 39.467 88.064 88.021 0 48.597-39.467 88.107-88.064 88.107zM303.744 872.619h-152.064v-488.619h152.064zM948.267 0h-872.704c-41.771 0-75.563 33.024-75.563 73.771v876.459c0 40.789 33.792 73.771 75.563 73.771h872.576c41.728 0 75.861-32.981 75.861-73.771v-876.459c0-40.747-34.133-73.771-75.861-73.771z"></path>
|
||||
</g>
|
||||
<g id="github">
|
||||
<path class="path1"
|
||||
d="M512 12.672c-282.88 0-512 229.248-512 512 0 226.261 146.688 418.133 350.080 485.76 25.6 4.821 34.987-11.008 34.987-24.619 0-12.16-0.427-44.373-0.64-87.040-142.421 30.891-172.459-68.693-172.459-68.693-23.296-59.093-56.96-74.88-56.96-74.88-46.379-31.744 3.584-31.104 3.584-31.104 51.413 3.584 78.421 52.736 78.421 52.736 45.653 78.293 119.851 55.68 149.12 42.581 4.608-33.109 17.792-55.68 32.427-68.48-113.707-12.8-233.216-56.832-233.216-253.013 0-55.893 19.84-101.547 52.693-137.387-5.76-12.928-23.040-64.981 4.48-135.509 0 0 42.88-13.739 140.8 52.48 40.96-11.392 84.48-17.024 128-17.28 43.52 0.256 87.040 5.888 128 17.28 97.28-66.219 140.16-52.48 140.16-52.48 27.52 70.528 10.24 122.581 5.12 135.509 32.64 35.84 52.48 81.493 52.48 137.387 0 196.693-119.68 240-233.6 252.587 17.92 15.36 34.56 46.763 34.56 94.72 0 68.523-0.64 123.563-0.64 140.203 0 13.44 8.96 29.44 35.2 24.32 204.843-67.157 351.403-259.157 351.403-485.077 0-282.752-229.248-512-512-512z"></path>
|
||||
</g>
|
||||
<g id="docker">
|
||||
<path fill="#1488c6" style="fill: var(--color2, #1488c6)" class="path1"
|
||||
d="M205.653 737.067c-29.184 0-55.637-23.893-55.637-52.907s23.893-53.035 55.68-53.035c31.915 0 55.893 23.893 55.893 52.992s-26.539 52.907-55.936 52.949zM888.832 448.512c-5.76-42.325-32-76.8-66.56-103.253l-13.44-10.667-10.837 13.227c-21.077 23.893-29.44 66.261-26.88 97.92 2.56 23.979 10.24 47.787 23.637 66.304-10.837 5.547-24.235 10.667-34.56 16.085-24.32 7.979-47.957 10.667-71.68 10.667h-684.373l-2.56 15.787c-5.12 50.432 2.56 103.253 23.979 151.040l10.411 18.56v2.56c64 105.941 177.92 153.6 301.995 153.6 238.677 0 434.432-103.253 527.232-325.675 60.8 2.645 122.197-13.227 151.040-71.509l7.68-13.227-12.8-7.979c-34.56-21.077-81.92-23.893-121.6-13.227zM547.157 406.187h-103.595v103.253h103.68v-103.339zM547.157 276.352h-103.595v103.253h103.68v-103.125zM547.157 143.915h-103.595v103.253h103.68v-103.253zM673.877 406.187h-102.997v103.253h103.253v-103.339zM289.963 406.187h-102.955v103.253h103.339v-103.339zM419.243 406.187h-102.4v103.253h102.997v-103.339zM161.963 406.187h-102.229v103.253h103.595v-103.339zM419.243 276.352h-102.4v103.253h102.997v-103.125zM289.323 276.352h-102.144v103.253h102.955v-103.125z"></path>
|
||||
</g>
|
||||
</defs>
|
||||
</svg>
|
||||
</iron-iconset-svg>`;
|
||||
|
||||
document.head.appendChild(template.content);
|
@ -1,32 +0,0 @@
|
||||
/******************************************************************************
|
||||
* This file is auto-generated by Vaadin.
|
||||
* If you want to customize the entry point, you can copy this file or create
|
||||
* your own `index.ts` in your frontend directory.
|
||||
* By default, the `index.ts` file should be in `./frontend/` folder.
|
||||
*
|
||||
* NOTE:
|
||||
* - You need to restart the dev-server after adding the new `index.ts` file.
|
||||
* After that, all modifications to `index.ts` are recompiled automatically.
|
||||
* - `index.js` is also supported if you don't want to use TypeScript.
|
||||
******************************************************************************/
|
||||
|
||||
// import Vaadin client-router to handle client-side and server-side navigation
|
||||
import { Router } from '@vaadin/router';
|
||||
|
||||
// import Flow module to enable navigation to Vaadin server-side views
|
||||
import { Flow } from '@vaadin/flow-frontend/Flow';
|
||||
|
||||
const { serverSideRoutes } = new Flow({
|
||||
imports: () => import('../../build/frontend/generated-flow-imports')
|
||||
});
|
||||
|
||||
const routes = [
|
||||
// for client-side, place routes below (more info https://vaadin.com/docs/v15/flow/typescript/creating-routes.html)
|
||||
|
||||
// for server-side, the next magic line sends all unmatched routes:
|
||||
...serverSideRoutes // IMPORTANT: this must be the last entry in the array
|
||||
];
|
||||
|
||||
// Vaadin router needs an outlet in the index.html page to display views
|
||||
const router = new Router(document.querySelector('#outlet'));
|
||||
router.setRoutes(routes);
|
@ -1,10 +0,0 @@
|
||||
// @ts-nocheck
|
||||
window.Vaadin = window.Vaadin || {};
|
||||
window.Vaadin.featureFlags = window.Vaadin.featureFlags || {};
|
||||
window.Vaadin.featureFlags.exampleFeatureFlag = false;
|
||||
window.Vaadin.featureFlags.viteForFrontendBuild = false;
|
||||
window.Vaadin.featureFlags.mapComponent = false;
|
||||
window.Vaadin.featureFlags.spreadsheetComponent = false;
|
||||
window.Vaadin.featureFlags.hillaPush = false;
|
||||
window.Vaadin.featureFlags.newLicenseChecker = false;
|
||||
window.Vaadin.featureFlags.collaborationEngineBackend = false;
|
@ -1,3 +0,0 @@
|
||||
import './vaadin-featureflags.ts';
|
||||
|
||||
import './index';
|
@ -3,10 +3,10 @@
|
||||
This file is auto-generated by Vaadin.
|
||||
-->
|
||||
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
body, #outlet {
|
||||
height: 100vh;
|
||||
@ -17,7 +17,7 @@ This file is auto-generated by Vaadin.
|
||||
<!-- index.ts is included here automatically (either by the dev server or during the build) -->
|
||||
</head>
|
||||
<body>
|
||||
<!-- This outlet div is where the views are rendered -->
|
||||
<div id="outlet"></div>
|
||||
<!-- This outlet div is where the views are rendered -->
|
||||
<div id="outlet"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,4 +5,4 @@ theArchivesBaseName=allure-server
|
||||
org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.jvmargs=-Xmx2024m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx2024m -Dfile.encoding=UTF-8
|
@ -1,33 +0,0 @@
|
||||
////// checkstyle //////
|
||||
checkstyle {
|
||||
toolVersion = '8.30'
|
||||
}
|
||||
////// pmd //////
|
||||
pmd {
|
||||
toolVersion = "6.21.0"
|
||||
rulePriority = 4 // 5 is default
|
||||
ruleSetFiles "$rootDir/config/pmd/pmd-rules.xml"
|
||||
ruleSets = []
|
||||
}
|
||||
pmdTest {
|
||||
rulePriority = 1
|
||||
}
|
||||
////// spotbugs //////
|
||||
spotbugs {
|
||||
ignoreFailures = false
|
||||
effort = 'default'
|
||||
reportLevel = 'high'
|
||||
excludeFilter = file("$rootDir/config/spotbug/spotbugs-exclude.xml")
|
||||
}
|
||||
spotbugsTest {
|
||||
reports {
|
||||
xml.enabled false
|
||||
html.enabled true
|
||||
}
|
||||
}
|
||||
spotbugsMain {
|
||||
reports {
|
||||
xml.enabled false
|
||||
html.enabled true
|
||||
}
|
||||
}
|
@ -3,17 +3,11 @@ repositories {
|
||||
}
|
||||
ext {
|
||||
qaLibVersion = '1.2.0'
|
||||
vaadinVersion = '23.1.3'
|
||||
guavaVersion = '31.1-jre'
|
||||
apacheIoVersion = '2.11.0'
|
||||
openApiVersion = '1.6.9'
|
||||
allureVersion = '2.18.1'
|
||||
}
|
||||
configurations {
|
||||
developmentOnly
|
||||
runtimeClasspath {
|
||||
extendsFrom developmentOnly
|
||||
}
|
||||
vaadinVersion = '24.4.4'
|
||||
guavaVersion = '33.2.1-jre'
|
||||
apacheIoVersion = '2.16.1'
|
||||
openApiVersion = '1.8.0'
|
||||
allureVersion = '2.29.0'
|
||||
}
|
||||
dependencyManagement {
|
||||
imports {
|
||||
@ -30,7 +24,8 @@ dependencies {
|
||||
/* Guava */
|
||||
implementation "com.google.guava:guava:$guavaVersion"
|
||||
/* Open API */
|
||||
implementation "org.springdoc:springdoc-openapi-ui:$openApiVersion"
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.2'
|
||||
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0"
|
||||
|
||||
/* OAuth2 */
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
@ -41,10 +36,10 @@ dependencies {
|
||||
implementation('org.springframework.boot:spring-boot-starter-web') { exclude group: 'ch.qos.logback' }
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
||||
implementation 'org.hibernate:hibernate-validator:6.2.3.Final'
|
||||
implementation 'org.hibernate:hibernate-validator:8.0.1.Final'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
|
||||
implementation 'org.jooq:joor:0.9.14'
|
||||
implementation 'org.jooq:joor:0.9.15'
|
||||
|
||||
implementation('com.vaadin:vaadin-spring-boot-starter') {
|
||||
|
||||
@ -55,8 +50,7 @@ dependencies {
|
||||
"org.webjars.bowergithub.vaadin",
|
||||
"org.webjars.bowergithub.webcomponents"].forEach { group -> exclude(group: group) }
|
||||
|
||||
['vaadin-dev-server',
|
||||
'vaadin-accordion-flow',
|
||||
['vaadin-accordion-flow',
|
||||
'vaadin-avatar-flow',
|
||||
'vaadin-date-picker-flow',
|
||||
'vaadin-date-time-picker-flow',
|
||||
@ -75,21 +69,18 @@ dependencies {
|
||||
'flow-dnd',
|
||||
'android-json'].forEach { module -> exclude(module: module) }
|
||||
}
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
|
||||
implementation "commons-io:commons-io:$apacheIoVersion"
|
||||
/* Logging API*/
|
||||
implementation 'org.slf4j:slf4j-api'
|
||||
/* Logging IMPL*/
|
||||
runtimeOnly 'ch.qos.logback:logback-classic'
|
||||
runtimeOnly 'com.h2database:h2:1.4.200'
|
||||
runtimeOnly 'com.h2database:h2:2.2.224'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
/* Testing */
|
||||
testImplementation('junit:junit') { transitive = false }
|
||||
testImplementation 'org.assertj:assertj-core'
|
||||
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude group: 'org.junit.vintage'
|
||||
exclude group: 'org.junit.jupiter'
|
||||
exclude group: 'ch.qos.logback'
|
||||
}
|
||||
}
|
||||
testImplementation('org.springframework.boot:spring-boot-starter-test')
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter'
|
||||
implementation 'org.springframework.boot:spring-boot-loader-tools'
|
||||
}
|
||||
|
2
gradle/gradle-daemon-jvm.properties
Normal file
2
gradle/gradle-daemon-jvm.properties
Normal file
@ -0,0 +1,2 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainVersion=21
|
@ -1,11 +1,13 @@
|
||||
////// junit //////
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
|
||||
minHeapSize = "256m"
|
||||
maxHeapSize = "2G"
|
||||
reports {
|
||||
junitXml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
// reports {
|
||||
// junitXml.enabled = true
|
||||
// html.enabled = true
|
||||
// }
|
||||
testLogging {
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
@ -13,26 +15,4 @@ test {
|
||||
/* events "started", "skipped", "failed" */
|
||||
exceptionFormat "full"
|
||||
}
|
||||
jacoco {
|
||||
enabled = true
|
||||
}
|
||||
finalizedBy jacocoTestReport
|
||||
}
|
||||
////// jacoco //////
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled true
|
||||
csv.enabled false
|
||||
html.enabled true
|
||||
}
|
||||
}
|
||||
jacocoTestCoverageVerification {
|
||||
mustRunAfter jacocoTestReport
|
||||
violationRules {
|
||||
rule {
|
||||
limit {
|
||||
minimum = 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
249
gradlew
vendored
Executable file
249
gradlew
vendored
Executable file
@ -0,0 +1,249 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
25071
package-lock.json
generated
25071
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,15 +11,17 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
import ru.iopump.qa.allure.properties.BasicProperties;
|
||||
import ru.iopump.qa.allure.properties.CleanUpProperties;
|
||||
import ru.iopump.qa.allure.properties.TmsProperties;
|
||||
|
||||
// @ImportAutoConfiguration({FeignAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class})
|
||||
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ErrorMvcAutoConfiguration.class})
|
||||
@EnableCaching
|
||||
@EnableTransactionManagement
|
||||
@EnableConfigurationProperties({AllureProperties.class, CleanUpProperties.class, BasicProperties.class})
|
||||
@EnableConfigurationProperties({AllureProperties.class, CleanUpProperties.class, BasicProperties.class, TmsProperties.class})
|
||||
@EnableVaadin
|
||||
public class Application { //NOPMD
|
||||
|
||||
public static void main(String[] args) { //NOPMD
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
package ru.iopump.qa.allure.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.Retryer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import ru.iopump.qa.allure.properties.TmsProperties;
|
||||
|
||||
import static org.springframework.cloud.openfeign.security.OAuth2AccessTokenInterceptor.AUTHORIZATION;
|
||||
import static org.springframework.cloud.openfeign.security.OAuth2AccessTokenInterceptor.BEARER;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@EnableFeignClients(basePackages = {"ru.iopump.qa.allure.api"})
|
||||
@ImportAutoConfiguration({FeignAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class})
|
||||
public class FeignConfiguration {
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor feignRequestInterceptor(TmsProperties props) {
|
||||
return requestTemplate -> {
|
||||
var token = props.getToken();
|
||||
var hasAuthorization = requestTemplate.headers().containsKey(AUTHORIZATION) && requestTemplate.headers().get(AUTHORIZATION).contains(token);
|
||||
if (!hasAuthorization) {
|
||||
requestTemplate.removeHeader(AUTHORIZATION);
|
||||
requestTemplate.header(AUTHORIZATION, BEARER + " " + token);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Retryer retryer() {
|
||||
return new Retryer.Default(100, 1000, 2);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
|
||||
return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package ru.iopump.qa.allure.api.youtrack;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
|
||||
@FeignClient(name = "youtrack-issues", url = "${tms.api-base-url}")
|
||||
public interface IssuesClient extends org.brewcode.api.youtrack.IssuesApi {
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package ru.iopump.qa.allure.config;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -14,8 +16,6 @@ import org.springframework.web.servlet.resource.PathResourceResolver;
|
||||
import org.springframework.web.servlet.resource.ResourceResolverChain;
|
||||
import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -51,30 +51,30 @@ public class RedirectConfiguration implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry
|
||||
.addResourceHandler(join("/", cfg.reports().dir(), "**"))
|
||||
.addResourceLocations("file:" + cfg.reports().dir())
|
||||
.resourceChain(true)
|
||||
.addResolver(new PathResourceResolver() {
|
||||
.addResourceHandler(join("/", cfg.reports().dir(), "**"))
|
||||
.addResourceLocations("file:" + cfg.reports().dir())
|
||||
.resourceChain(true)
|
||||
.addResolver(new PathResourceResolver() {
|
||||
|
||||
@Override
|
||||
public Resource resolveResource(HttpServletRequest request,
|
||||
@Nonnull String requestPath,
|
||||
@Nonnull List<? extends Resource> locations,
|
||||
@Nonnull ResourceResolverChain chain) {
|
||||
return super.resolveResource(request, requestPath, locations, chain);
|
||||
@Override
|
||||
public Resource resolveResource(HttpServletRequest request,
|
||||
@Nonnull String requestPath,
|
||||
@Nonnull List<? extends Resource> locations,
|
||||
@Nonnull ResourceResolverChain chain) {
|
||||
return super.resolveResource(request, requestPath, locations, chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource getResource(@Nonnull String resourcePath,
|
||||
@Nonnull Resource location) throws IOException {
|
||||
var res = super.getResource(resourcePath, location);
|
||||
if (res == null) {
|
||||
return getIndexHtml(resourcePath, location);
|
||||
}
|
||||
return res;
|
||||
|
||||
@Override
|
||||
protected Resource getResource(@Nonnull String resourcePath,
|
||||
@Nonnull Resource location) throws IOException {
|
||||
var res = super.getResource(resourcePath, location);
|
||||
if (res == null) {
|
||||
return getIndexHtml(resourcePath, location);
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@ -83,11 +83,11 @@ public class RedirectConfiguration implements WebMvcConfigurer {
|
||||
final Path thisResource = location.getFile().toPath().resolve(resourcePath);
|
||||
if (Files.exists(thisResource) && Files.isDirectory(thisResource)) {
|
||||
return Files.walk(thisResource, 1)
|
||||
.skip(1)
|
||||
.filter(i -> "index.html".equals(i.getFileName().toString()))
|
||||
.map(i -> new FileSystemResource(i.toFile()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
.skip(1)
|
||||
.filter(i -> "index.html".equals(i.getFileName().toString()))
|
||||
.map(i -> new FileSystemResource(i.toFile()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package ru.iopump.qa.allure.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import ru.iopump.qa.allure.helper.plugin.AllureServerPlugin;
|
||||
import ru.iopump.qa.util.ReflectionUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class SpringConfiguration {
|
||||
|
||||
@Bean
|
||||
public Collection<AllureServerPlugin> allureServerPlugins() {
|
||||
var plugins = ReflectionUtil.createImplementations(AllureServerPlugin.class, null);
|
||||
log.info("[ALLURE SERVER CONFIGURATION] Allure server plugins loaded: {}", plugins.stream().map(SpringConfiguration::name).toList());
|
||||
return plugins;
|
||||
}
|
||||
|
||||
private static String name(AllureServerPlugin plugin) {
|
||||
return plugin.getClass() + ":" + plugin.getName();
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@ import io.qameta.allure.entity.ExecutorInfo;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
@ -14,7 +17,16 @@ import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.iopump.qa.allure.entity.ReportEntity;
|
||||
import ru.iopump.qa.allure.model.ReportGenerateRequest;
|
||||
@ -24,9 +36,6 @@ import ru.iopump.qa.allure.service.JpaReportService;
|
||||
import ru.iopump.qa.allure.service.ResultService;
|
||||
import ru.iopump.qa.util.StreamUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
|
@ -4,6 +4,10 @@ import com.google.common.base.Preconditions;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -13,7 +17,15 @@ import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.iopump.qa.allure.model.ResultResponse;
|
||||
import ru.iopump.qa.allure.model.UploadResponse;
|
||||
@ -21,10 +33,6 @@ import ru.iopump.qa.allure.service.PathUtil;
|
||||
import ru.iopump.qa.allure.service.ResultService;
|
||||
import ru.iopump.qa.util.StreamUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -1,6 +1,16 @@
|
||||
package ru.iopump.qa.allure.entity;
|
||||
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.PositiveOrZero;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -8,11 +18,6 @@ import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.PositiveOrZero;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
|
@ -2,10 +2,9 @@ package ru.iopump.qa.allure.gui;
|
||||
|
||||
import com.vaadin.flow.component.applayout.AppLayout;
|
||||
import com.vaadin.flow.component.applayout.DrawerToggle;
|
||||
import com.vaadin.flow.component.dependency.JsModule;
|
||||
import com.vaadin.flow.component.html.Anchor;
|
||||
import com.vaadin.flow.component.html.H3;
|
||||
import com.vaadin.flow.component.icon.IronIcon;
|
||||
import com.vaadin.flow.component.icon.SvgIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
@ -13,16 +12,19 @@ import com.vaadin.flow.component.tabs.Tab;
|
||||
import com.vaadin.flow.component.tabs.Tabs;
|
||||
import com.vaadin.flow.router.HighlightConditions;
|
||||
import com.vaadin.flow.router.RouterLink;
|
||||
import com.vaadin.flow.server.StreamResource;
|
||||
import ru.iopump.qa.allure.gui.view.AboutView;
|
||||
import ru.iopump.qa.allure.gui.view.ReportsView;
|
||||
import ru.iopump.qa.allure.gui.view.ResultsView;
|
||||
import ru.iopump.qa.allure.gui.view.SwaggerView;
|
||||
|
||||
@JsModule("./brands.js")
|
||||
import java.io.Serial;
|
||||
|
||||
public class MainLayout extends AppLayout {
|
||||
|
||||
public static final String ALLURE_SERVER = "Allure Server";
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 2881152775131362224L;
|
||||
|
||||
public MainLayout() {
|
||||
@ -57,13 +59,13 @@ public class MainLayout extends AppLayout {
|
||||
tabs.setSizeFull();
|
||||
|
||||
var github = new Anchor("https://github.com/kochetkov-ma/allure-server",
|
||||
new IronIcon("icomoon", "github"));
|
||||
new SvgIcon(new StreamResource("github.svg", () -> getClass().getResourceAsStream("/icons/github.svg"))));
|
||||
github.setTarget("_blank");
|
||||
var dockerHub = new Anchor("https://hub.docker.com/r/kochetkovma/allure-server",
|
||||
new IronIcon("icomoon", "docker"));
|
||||
new SvgIcon(new StreamResource("docker.svg", () -> getClass().getResourceAsStream("/icons/docker.svg"))));
|
||||
dockerHub.setTarget("_blank");
|
||||
var linkedIn = new Anchor("https://www.linkedin.com/in/maxim-kochetkov-75178215a/",
|
||||
new IronIcon("icomoon", "linkedin"));
|
||||
new SvgIcon(new StreamResource("linkedin.svg", () -> getClass().getResourceAsStream("/icons/linkedin.svg"))));
|
||||
linkedIn.setTarget("_blank");
|
||||
|
||||
var footer = new HorizontalLayout(github, dockerHub, linkedIn);
|
||||
|
@ -124,7 +124,7 @@ public class FilteredGrid<T> {
|
||||
grid.addClassName(GRID_CLASS);
|
||||
grid.setDataProvider(dataProvider);
|
||||
grid.removeAllColumns();
|
||||
grid.setHeightByRows(true);
|
||||
// grid.setHeightByRows(true); // deprecated
|
||||
grid.setSelectionMode(Grid.SelectionMode.MULTI);
|
||||
|
||||
final List<Grid.Column<T>> cols = columnSpecList.stream()
|
||||
|
@ -7,7 +7,7 @@ import com.vaadin.flow.component.checkbox.Checkbox;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
||||
import com.vaadin.flow.component.html.H3;
|
||||
import com.vaadin.flow.component.html.Label;
|
||||
import com.vaadin.flow.component.html.NativeLabel;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.data.binder.BeanValidationBinder;
|
||||
@ -32,8 +32,8 @@ public class ReportGenerateDialog extends Dialog {
|
||||
|
||||
private final AllureReportController allureReportController;
|
||||
|
||||
private final Label info = new Label();
|
||||
private final Label error = new Label();
|
||||
private final NativeLabel info = new NativeLabel();
|
||||
private final NativeLabel error = new NativeLabel();
|
||||
@Getter
|
||||
private final FormPayload payload;
|
||||
private final Button generate = new Button("Generate", e -> onClickGenerate());
|
||||
|
@ -6,16 +6,16 @@ import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.H3;
|
||||
import com.vaadin.flow.component.html.Label;
|
||||
import com.vaadin.flow.component.html.NativeLabel;
|
||||
import com.vaadin.flow.component.html.Paragraph;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.upload.Upload;
|
||||
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -33,16 +33,16 @@ public class ResultUploadDialog extends Dialog { //NOPMD
|
||||
private final Button close = new Button("Ok", e -> onClickCloseAndDiscard());
|
||||
|
||||
public ResultUploadDialog(
|
||||
Function<MemoryBuffer, Object> uploadConsumer,
|
||||
int maxFileSizeBytes,
|
||||
String type) {
|
||||
Function<MemoryBuffer, Object> uploadConsumer,
|
||||
int maxFileSizeBytes,
|
||||
String type) {
|
||||
|
||||
this.buffer = new MemoryBuffer();
|
||||
this.infoContainer = new Div();
|
||||
|
||||
this.upload = new Upload(buffer);
|
||||
upload.setMaxFiles(1);
|
||||
upload.setDropLabel(new Label(format("Upload allure {} as Zip archive (.zip)", type)));
|
||||
upload.setDropLabel(new NativeLabel(format("Upload allure {} as Zip archive (.zip)", type)));
|
||||
upload.setAcceptedFileTypes(".zip");
|
||||
upload.setMaxFileSize(maxFileSizeBytes);
|
||||
|
||||
@ -50,9 +50,9 @@ public class ResultUploadDialog extends Dialog { //NOPMD
|
||||
try {
|
||||
var uploadResponse = uploadConsumer.apply(buffer);
|
||||
show(info(format(
|
||||
"File '{}- {} bytes' loaded: {}",
|
||||
event.getFileName(), event.getContentLength(), uploadResponse
|
||||
)), false
|
||||
"File '{}- {} bytes' loaded: {}",
|
||||
event.getFileName(), event.getContentLength(), uploadResponse
|
||||
)), false
|
||||
);
|
||||
} catch (Exception ex) { //NOPMD
|
||||
show(error("Internal error: " + ex.getLocalizedMessage()), true);
|
||||
|
@ -1,8 +1,8 @@
|
||||
package ru.iopump.qa.allure.gui.dto;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
@ -11,6 +11,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.data.provider.ListDataProvider;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
|
||||
import ru.iopump.qa.allure.controller.AllureReportController;
|
||||
@ -23,7 +24,6 @@ import ru.iopump.qa.allure.gui.component.ResultUploadDialog;
|
||||
import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
import ru.iopump.qa.allure.service.JpaReportService;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -61,34 +61,34 @@ public class ReportsView extends VerticalLayout {
|
||||
this.dateTimeResolver.retrieve();
|
||||
|
||||
this.uploadDialog = new ResultUploadDialog(
|
||||
(buffer) -> allureReportController.uploadReport("manual_uploaded", toMultiPartFile(buffer)),
|
||||
(int) multipartProperties.getMaxFileSize().toBytes(),
|
||||
"report"
|
||||
(buffer) -> allureReportController.uploadReport("manual_uploaded", toMultiPartFile(buffer)),
|
||||
(int) multipartProperties.getMaxFileSize().toBytes(),
|
||||
"report"
|
||||
);
|
||||
|
||||
this.reports = new FilteredGrid<>(
|
||||
asProvider(jpaReportService),
|
||||
cols()
|
||||
asProvider(jpaReportService),
|
||||
cols()
|
||||
);
|
||||
this.uploadButton = new Button("Upload report");
|
||||
this.deleteSelection = new Button("Delete selection",
|
||||
new Icon(VaadinIcon.CLOSE_CIRCLE),
|
||||
event -> {
|
||||
for (ReportEntity reportEntity : reports.getGrid().getSelectedItems()) {
|
||||
UUID uuid = reportEntity.getUuid();
|
||||
try {
|
||||
jpaReportService.internalDeleteByUUID(uuid);
|
||||
Notification.show("Delete success: " + uuid, 2000, Notification.Position.TOP_START);
|
||||
} catch (Exception e) { //NOPMD
|
||||
Notification.show("Deleting error: " + e.getLocalizedMessage(),
|
||||
5000,
|
||||
Notification.Position.TOP_START);
|
||||
log.error("Deleting error", e);
|
||||
}
|
||||
new Icon(VaadinIcon.CLOSE_CIRCLE),
|
||||
event -> {
|
||||
for (ReportEntity reportEntity : reports.getGrid().getSelectedItems()) {
|
||||
UUID uuid = reportEntity.getUuid();
|
||||
try {
|
||||
jpaReportService.internalDeleteByUUID(uuid);
|
||||
Notification.show("Delete success: " + uuid, 2000, Notification.Position.TOP_START);
|
||||
} catch (Exception e) { //NOPMD
|
||||
Notification.show("Deleting error: " + e.getLocalizedMessage(),
|
||||
5000,
|
||||
Notification.Position.TOP_START);
|
||||
log.error("Deleting error", e);
|
||||
}
|
||||
reports.getGrid().deselectAll();
|
||||
reports.getGrid().getDataProvider().refreshAll();
|
||||
});
|
||||
}
|
||||
reports.getGrid().deselectAll();
|
||||
reports.getGrid().getDataProvider().refreshAll();
|
||||
});
|
||||
deleteSelection.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||
|
||||
this.dateTimeResolver.onClientReady(() -> reports.getGrid().getDataProvider().refreshAll());
|
||||
@ -99,9 +99,9 @@ public class ReportsView extends VerticalLayout {
|
||||
private static ListDataProvider<ReportEntity> asProvider(final JpaReportService jpaReportService) {
|
||||
//noinspection unchecked
|
||||
final Collection<ReportEntity> collection = (Collection<ReportEntity>) Proxy
|
||||
.newProxyInstance(Thread.currentThread().getContextClassLoader(),
|
||||
new Class[]{Collection.class},
|
||||
(proxy, method, args) -> method.invoke(jpaReportService.getAll(), args));
|
||||
.newProxyInstance(Thread.currentThread().getContextClassLoader(),
|
||||
new Class[]{Collection.class},
|
||||
(proxy, method, args) -> method.invoke(jpaReportService.getAll(), args));
|
||||
|
||||
return new ListDataProvider<>(collection);
|
||||
}
|
||||
@ -109,19 +109,19 @@ public class ReportsView extends VerticalLayout {
|
||||
//// PRIVATE ////
|
||||
private List<Col<ReportEntity>> cols() {
|
||||
return ImmutableList.<Col<ReportEntity>>builder()
|
||||
.add(Col.<ReportEntity>with().name("Uuid").value(prop("uuid")).build())
|
||||
.add(
|
||||
Col.<ReportEntity>with()
|
||||
.name("Created")
|
||||
.value(e -> dateTimeResolver.printDate(e.getCreatedDateTime()))
|
||||
.build()
|
||||
)
|
||||
.add(Col.<ReportEntity>with().name("Url").value(this::displayUrl).type(LINK).build())
|
||||
.add(Col.<ReportEntity>with().name("Path").value(prop("path")).build())
|
||||
.add(Col.<ReportEntity>with().name("Active").value(prop("active")).build())
|
||||
.add(Col.<ReportEntity>with().name("Size KB").value(prop("size")).type(NUMBER).build())
|
||||
.add(Col.<ReportEntity>with().name("Build").value(this::buildUrl).type(LINK).build())
|
||||
.build();
|
||||
.add(Col.<ReportEntity>with().name("Uuid").value(prop("uuid")).build())
|
||||
.add(
|
||||
Col.<ReportEntity>with()
|
||||
.name("Created")
|
||||
.value(e -> dateTimeResolver.printDate(e.getCreatedDateTime()))
|
||||
.build()
|
||||
)
|
||||
.add(Col.<ReportEntity>with().name("Url").value(this::displayUrl).type(LINK).build())
|
||||
.add(Col.<ReportEntity>with().name("Path").value(prop("path")).build())
|
||||
.add(Col.<ReportEntity>with().name("Active").value(prop("active")).build())
|
||||
.add(Col.<ReportEntity>with().name("Size KB").value(prop("size")).type(NUMBER).build())
|
||||
.add(Col.<ReportEntity>with().name("Build").value(this::buildUrl).type(LINK).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private String buildUrl(ReportEntity e) {
|
||||
|
@ -12,6 +12,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.data.provider.ListDataProvider;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
|
||||
import ru.iopump.qa.allure.controller.AllureReportController;
|
||||
@ -26,7 +27,6 @@ import ru.iopump.qa.allure.gui.dto.GenerateDto;
|
||||
import ru.iopump.qa.allure.model.ResultResponse;
|
||||
import ru.iopump.qa.util.StreamUtil;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -5,10 +5,11 @@ import com.vaadin.flow.component.html.IFrame;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import ru.iopump.qa.allure.gui.MainLayout;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import java.io.Serial;
|
||||
|
||||
import static ru.iopump.qa.allure.gui.MainLayout.ALLURE_SERVER;
|
||||
import static ru.iopump.qa.allure.helper.Util.concatParts;
|
||||
@ -19,6 +20,7 @@ import static ru.iopump.qa.allure.helper.Util.concatParts;
|
||||
@Slf4j
|
||||
public class SwaggerView extends VerticalLayout {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5822077036734476962L;
|
||||
|
||||
public SwaggerView(ServletContext context) {
|
||||
|
@ -1,100 +1,62 @@
|
||||
package ru.iopump.qa.allure.helper; //NOPMD
|
||||
|
||||
import io.qameta.allure.Aggregator2;
|
||||
import io.qameta.allure.ConfigurationBuilder;
|
||||
import io.qameta.allure.ReportGenerator;
|
||||
import io.qameta.allure.allure1.Allure1Plugin;
|
||||
import io.qameta.allure.allure2.Allure2Plugin;
|
||||
import io.qameta.allure.category.CategoriesPlugin;
|
||||
import io.qameta.allure.category.CategoriesTrendPlugin;
|
||||
import io.qameta.allure.context.FreemarkerContext;
|
||||
import io.qameta.allure.context.JacksonContext;
|
||||
import io.qameta.allure.context.MarkdownContext;
|
||||
import io.qameta.allure.context.RandomUidContext;
|
||||
import io.qameta.allure.core.AttachmentsPlugin;
|
||||
import io.qameta.allure.ReportStorage;
|
||||
import io.qameta.allure.core.Configuration;
|
||||
import io.qameta.allure.core.MarkdownDescriptionsPlugin;
|
||||
import io.qameta.allure.core.LaunchResults;
|
||||
import io.qameta.allure.core.Plugin;
|
||||
import io.qameta.allure.core.ReportWebPlugin;
|
||||
import io.qameta.allure.core.TestsResultsPlugin;
|
||||
import io.qameta.allure.duration.DurationPlugin;
|
||||
import io.qameta.allure.duration.DurationTrendPlugin;
|
||||
import io.qameta.allure.environment.Allure1EnvironmentPlugin;
|
||||
import io.qameta.allure.history.HistoryPlugin;
|
||||
import io.qameta.allure.history.HistoryTrendPlugin;
|
||||
import io.qameta.allure.launch.LaunchPlugin;
|
||||
import io.qameta.allure.mail.MailPlugin;
|
||||
import io.qameta.allure.owner.OwnerPlugin;
|
||||
import io.qameta.allure.plugin.DefaultPluginLoader;
|
||||
import io.qameta.allure.retry.RetryPlugin;
|
||||
import io.qameta.allure.retry.RetryTrendPlugin;
|
||||
import io.qameta.allure.severity.SeverityPlugin;
|
||||
import io.qameta.allure.status.StatusChartPlugin;
|
||||
import io.qameta.allure.suites.SuitesPlugin;
|
||||
import io.qameta.allure.summary.SummaryPlugin;
|
||||
import io.qameta.allure.tags.TagsPlugin;
|
||||
import io.qameta.allure.timeline.TimelinePlugin;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.iopump.qa.allure.helper.plugin.AllureServerPlugin;
|
||||
import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
import ru.iopump.qa.allure.properties.TmsProperties;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public final class AllureReportGenerator {
|
||||
|
||||
private final Collection<AllureServerPlugin> listeners;
|
||||
private final AllureProperties allureProperties;
|
||||
private final TmsProperties tmsProperties;
|
||||
private final ReportGenerator delegate;
|
||||
private final BeanFactory beanFactory;
|
||||
private final AggregatorGrabber aggregatorGrabber = new AggregatorGrabber();
|
||||
|
||||
public AllureReportGenerator() {
|
||||
public AllureReportGenerator(@NonNull Collection<AllureServerPlugin> listeners, AllureProperties allureProperties, TmsProperties tmsProperties, BeanFactory beanFactory) {
|
||||
this.listeners = listeners;
|
||||
this.allureProperties = allureProperties;
|
||||
this.tmsProperties = tmsProperties;
|
||||
this.beanFactory = beanFactory;
|
||||
this.delegate = new ReportGenerator(configuration());
|
||||
}
|
||||
|
||||
private static Configuration configuration() {
|
||||
return new ConfigurationBuilder()
|
||||
.fromPlugins(loadPlugins())
|
||||
.fromExtensions(
|
||||
Arrays.asList(
|
||||
new JacksonContext(),
|
||||
new MarkdownContext(),
|
||||
new FreemarkerContext(),
|
||||
new RandomUidContext(),
|
||||
new MarkdownDescriptionsPlugin(),
|
||||
new RetryPlugin(),
|
||||
new RetryTrendPlugin(),
|
||||
new TagsPlugin(),
|
||||
new SeverityPlugin(),
|
||||
new OwnerPlugin(),
|
||||
new HistoryPlugin(),
|
||||
new HistoryTrendPlugin(),
|
||||
new CategoriesPlugin(),
|
||||
new CategoriesTrendPlugin(),
|
||||
new DurationPlugin(),
|
||||
new DurationTrendPlugin(),
|
||||
new StatusChartPlugin(),
|
||||
new TimelinePlugin(),
|
||||
new SuitesPlugin(),
|
||||
new ReportWebPlugin(),
|
||||
new TestsResultsPlugin(),
|
||||
new AttachmentsPlugin(),
|
||||
new MailPlugin(),
|
||||
new SummaryPlugin(),
|
||||
new ExecutorCiPlugin(),
|
||||
new LaunchPlugin(),
|
||||
new Allure2Plugin(),
|
||||
new Allure1EnvironmentPlugin(),
|
||||
new Allure1Plugin()
|
||||
)
|
||||
).build();
|
||||
private Configuration configuration() {
|
||||
return ConfigurationBuilder
|
||||
.bundled()
|
||||
.withPlugins(loadPlugins())
|
||||
.withExtensions(List.of(aggregatorGrabber))
|
||||
.build();
|
||||
}
|
||||
|
||||
///// PRIVATE /////
|
||||
@ -138,8 +100,75 @@ public final class AllureReportGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
public Path generate(Path outputDirectory, List<Path> resultsDirectories) throws IOException {
|
||||
delegate.generate(outputDirectory, resultsDirectories);
|
||||
public Path generate(Path outputDirectory, List<Path> resultsDirectories, String reportUrl) {
|
||||
var ctx = new PluginContext(reportUrl);
|
||||
|
||||
var effectiveListeners = listeners.stream()
|
||||
.filter(it -> {
|
||||
if (it.isEnabled(ctx))
|
||||
return true;
|
||||
log.info("[PLUGIN] Plugin '{} : {}' is disabled", it.getName(), it.getClass().getName());
|
||||
return false;
|
||||
})
|
||||
.toList();
|
||||
|
||||
effectiveListeners.parallelStream()
|
||||
.forEach(listener -> evaluateListener(() -> listener.onGenerationStart(resultsDirectories, ctx), listener.getName(), "before generation"));
|
||||
|
||||
final Collection<LaunchResults> launchesResults;
|
||||
synchronized (aggregatorGrabber) {
|
||||
delegate.generate(outputDirectory, resultsDirectories);
|
||||
launchesResults = aggregatorGrabber.launchesResults();
|
||||
}
|
||||
|
||||
effectiveListeners.parallelStream()
|
||||
.forEach(listener -> evaluateListener(() -> listener.onGenerationFinish(outputDirectory, launchesResults, ctx), listener.getName(), "after generation"));
|
||||
|
||||
return outputDirectory;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
private class PluginContext implements AllureServerPlugin.Context {
|
||||
|
||||
private final String reportUrl;
|
||||
|
||||
@Override
|
||||
public AllureProperties getAllureProperties() {
|
||||
return allureProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TmsProperties tmsProperties() {
|
||||
return tmsProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanFactory beanFactory() {
|
||||
return beanFactory;
|
||||
}
|
||||
}
|
||||
|
||||
private static void evaluateListener(Runnable runnable, String name, String stage) {
|
||||
try {
|
||||
runnable.run();
|
||||
log.info("Listener '{}' {} executed successfully", name, stage);
|
||||
} catch (Exception ex) {
|
||||
log.error("Error in listener '{}'", name, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AggregatorGrabber implements Aggregator2 {
|
||||
|
||||
private Collection<LaunchResults> launchesResults = Collections.emptyList();
|
||||
|
||||
private Collection<LaunchResults> launchesResults() {
|
||||
return launchesResults.stream().toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aggregate(Configuration configuration, List<LaunchResults> launchesResults, ReportStorage storage) {
|
||||
this.launchesResults = launchesResults;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,102 +0,0 @@
|
||||
package ru.iopump.qa.allure.helper;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import ru.iopump.qa.allure.entity.ReportEntity;
|
||||
import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
import ru.iopump.qa.allure.service.PathUtil;
|
||||
import ru.iopump.qa.util.Str;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ru.iopump.qa.allure.gui.DateTimeResolver.zeroZone;
|
||||
|
||||
@Slf4j
|
||||
public class OldReportsFormatConverterHelper {
|
||||
|
||||
private final Path reportsDir;
|
||||
private final String reportsPath;
|
||||
|
||||
public OldReportsFormatConverterHelper(AllureProperties cfg) {
|
||||
this(Paths.get(cfg.reports().dir()), cfg.reports().path());
|
||||
}
|
||||
|
||||
OldReportsFormatConverterHelper(Path reportsDir, String reportsPath) {
|
||||
this.reportsDir = reportsDir;
|
||||
this.reportsPath = reportsPath;
|
||||
}
|
||||
|
||||
public Collection<ReportEntity> convertOldFormat() throws IOException {
|
||||
|
||||
if (hasOldFormatReports()) {
|
||||
|
||||
final Collection<Path> oldReports = Files.walk(reportsDir)
|
||||
.parallel()
|
||||
.filter(p -> "index.html".equalsIgnoreCase(p.getFileName().toString()))
|
||||
.map(Path::getParent)
|
||||
.filter(this::isOldFormat)
|
||||
.collect(Collectors.toList());
|
||||
log.info("Found '{}' old reports: {}", oldReports.size(), oldReports);
|
||||
|
||||
return oldReports.stream()
|
||||
.map(dir -> {
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
final File destination = reportsDir.resolve(uuid.toString()).toFile();
|
||||
final String thisReportPath = reportsDir.relativize(dir).toString();
|
||||
|
||||
try {
|
||||
FileUtils.moveDirectory(dir.toFile(), destination);
|
||||
FileUtils.deleteDirectory(dir.toFile());
|
||||
|
||||
log.info("Report moved from '{}' to '{}'", dir, destination);
|
||||
return ReportEntity.builder()
|
||||
.uuid(uuid)
|
||||
.path(thisReportPath)
|
||||
.createdDateTime(LocalDateTime.now(zeroZone()))
|
||||
.url(reportsPath + thisReportPath)
|
||||
.level(0)
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error(Str.frm("Error moving report '{}' to '{}'", dir, destination), e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fast check report directory.
|
||||
*/
|
||||
protected boolean hasOldFormatReports() throws IOException {
|
||||
|
||||
return !Files.walk(reportsDir, 1)
|
||||
.skip(1)
|
||||
.parallel()
|
||||
.allMatch(this::isNewFormat);
|
||||
}
|
||||
|
||||
protected boolean isNewFormat(Path p) {
|
||||
return Files.isDirectory(p) &&
|
||||
(p.getFileName().toString().matches(PathUtil.UUID_PATTERN)
|
||||
|| p.getFileName().toString().equalsIgnoreCase("history"));
|
||||
}
|
||||
|
||||
private boolean isOldFormat(Path p) {
|
||||
return !isNewFormat(p);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package ru.iopump.qa.allure.helper;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -11,7 +12,6 @@ import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
import static ru.iopump.qa.allure.helper.Util.concatParts;
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ru.iopump.qa.allure.helper;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
@ -7,13 +9,14 @@ import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
import ru.iopump.qa.util.Str;
|
||||
import ru.iopump.qa.util.StreamUtil;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("RedundantModifiersUtilityClassLombok")
|
||||
@UtilityClass
|
||||
public class Util {
|
||||
public static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
public static String url(AllureProperties allureProperties) {
|
||||
if (StringUtils.isBlank(allureProperties.serverBaseUrl())) {
|
||||
return ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString() + "/";
|
||||
@ -24,15 +27,15 @@ public class Util {
|
||||
|
||||
public static String concatParts(@Nullable String... part) {
|
||||
return StreamUtil.stream(part)
|
||||
.collect(Collectors.joining("/"))
|
||||
.replaceAll("/{2,}", "/");
|
||||
.collect(Collectors.joining("/"))
|
||||
.replaceAll("/{2,}", "/");
|
||||
}
|
||||
|
||||
public static String join(@Nullable Object... part) {
|
||||
return StreamUtil.stream(part)
|
||||
.map(Str::toStr)
|
||||
.map(s -> StringUtils.strip(s, "/"))
|
||||
.collect(Collectors.joining("/"));
|
||||
.map(Str::toStr)
|
||||
.map(s -> StringUtils.strip(s, "/"))
|
||||
.collect(Collectors.joining("/"));
|
||||
}
|
||||
|
||||
public static String shortUrl(@Nullable String str) {
|
||||
|
@ -0,0 +1,33 @@
|
||||
package ru.iopump.qa.allure.helper.plugin;
|
||||
|
||||
import io.qameta.allure.core.LaunchResults;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
import ru.iopump.qa.allure.properties.TmsProperties;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface AllureServerPlugin {
|
||||
|
||||
void onGenerationStart(Collection<Path> resultsDirectories, Context context);
|
||||
|
||||
void onGenerationFinish(Path reportDirectory, Collection<LaunchResults> launchResults, Context context);
|
||||
|
||||
String getName();
|
||||
|
||||
default boolean isEnabled(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
|
||||
AllureProperties getAllureProperties();
|
||||
|
||||
TmsProperties tmsProperties();
|
||||
|
||||
BeanFactory beanFactory();
|
||||
|
||||
String getReportUrl();
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package ru.iopump.qa.allure.helper.plugin;
|
||||
|
||||
import io.qameta.allure.core.LaunchResults;
|
||||
import io.qameta.allure.summary.SummaryData;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.StandardOpenOption.CREATE_NEW;
|
||||
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||
import static ru.iopump.qa.allure.helper.Util.MAPPER;
|
||||
|
||||
@Slf4j
|
||||
public class CustomReportMetaPlugin implements AllureServerPlugin {
|
||||
|
||||
private final static String LOGO_FILE = "logo.png";
|
||||
private final static String LOGO_DIR = "plugin/custom-logo";
|
||||
private final static String LOGO_STYLE_FILE = "styles.css";
|
||||
|
||||
private final static String SUMMARY_DIR = "widgets/summary.json";
|
||||
|
||||
@Override
|
||||
public void onGenerationStart(Collection<Path> resultsDirectories, Context context) {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void onGenerationFinish(Path reportDirectory, Collection<LaunchResults> launchResults, Context context) {
|
||||
|
||||
var logo = context.getAllureProperties().logo();
|
||||
|
||||
if (logo != null) {
|
||||
var bytes = logo.isReadable() ? logo.getContentAsByteArray() : null;
|
||||
if (bytes != null) {
|
||||
//noinspection resource
|
||||
var customLogoDirectory = Files
|
||||
.find(reportDirectory, 3, (path, basicFileAttributes) -> basicFileAttributes.isDirectory() && path.toString().endsWith(LOGO_DIR))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new InternalError("Custom logo plugin directory not found..."));
|
||||
|
||||
var logoName = Objects.requireNonNullElse(logo.getFilename(), LOGO_FILE);
|
||||
var customLogoPath = customLogoDirectory.resolve(logoName);
|
||||
var customLogoCssPath = customLogoDirectory.resolve(LOGO_STYLE_FILE);
|
||||
|
||||
Files.write(customLogoPath, bytes);
|
||||
|
||||
String cssForNewLogo = new String(new ClassPathResource("static/" + LOGO_STYLE_FILE).getContentAsByteArray(), UTF_8)
|
||||
.replace("img.png", logoName);
|
||||
Files.writeString(customLogoCssPath, cssForNewLogo, UTF_8, Files.exists(customLogoCssPath) ? TRUNCATE_EXISTING : CREATE_NEW);
|
||||
log.info("{}: {} copied to {}", getName(), logoName, customLogoPath);
|
||||
}
|
||||
}
|
||||
|
||||
var title = context.getAllureProperties().title();
|
||||
if (title != null) {
|
||||
//noinspection resource
|
||||
var summaryPath = Files
|
||||
.find(reportDirectory, 3, (path, basicFileAttributes) -> basicFileAttributes.isRegularFile() && path.toString().endsWith(SUMMARY_DIR))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new InternalError("Summary file not found..."));
|
||||
|
||||
var summaryData = MAPPER.readValue(summaryPath.toFile(), SummaryData.class);
|
||||
summaryData.setReportName(title);
|
||||
var newSummary = MAPPER.writeValueAsString(summaryData);
|
||||
Files.writeString(summaryPath, newSummary, UTF_8, TRUNCATE_EXISTING);
|
||||
log.info("{}: Summary file updated with new title: {}", getName(), title);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Logo Plugin";
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
package ru.iopump.qa.allure.helper.plugin;
|
||||
|
||||
import io.qameta.allure.core.LaunchResults;
|
||||
import io.qameta.allure.entity.Link;
|
||||
import io.qameta.allure.entity.Status;
|
||||
import io.qameta.allure.entity.TestResult;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.brewcode.api.youtrack.model.IssueCommentDto;
|
||||
import ru.iopump.qa.allure.api.youtrack.IssuesClient;
|
||||
import ru.iopump.qa.allure.helper.plugin.youtrack.MarkdownStatisticModel;
|
||||
import ru.iopump.qa.allure.helper.plugin.youtrack.MarkdownStatisticModel.Row.Statistic;
|
||||
import ru.iopump.qa.allure.helper.plugin.youtrack.MarkdownStatisticModel.Total;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.qameta.allure.entity.Status.SKIPPED;
|
||||
import static io.qameta.allure.entity.Status.UNKNOWN;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.apache.commons.lang3.StringUtils.substringBefore;
|
||||
import static ru.iopump.qa.allure.helper.Util.join;
|
||||
import static ru.iopump.qa.allure.helper.Util.url;
|
||||
import static ru.iopump.qa.allure.helper.plugin.youtrack.MarkdownStatisticModel.Row.Statistic.none;
|
||||
|
||||
// https://www.jetbrains.com/help/youtrack/devportal/youtrack-rest-api.html
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class YouTrackPlugin implements AllureServerPlugin {
|
||||
|
||||
private static final String COMMENT_TITLE = "E2E testing scenarios";
|
||||
private static final String ALLURE_SERVER = "Allure Server";
|
||||
private final boolean dryRun;
|
||||
|
||||
public YouTrackPlugin() {
|
||||
this.dryRun = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(Context context) {
|
||||
return context.tmsProperties().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenerationStart(Collection<Path> resultsDirectories, Context context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenerationFinish(Path reportDirectory, Collection<LaunchResults> launchResults, Context context) {
|
||||
final var effectiveDryRun = context.tmsProperties().isDryRun() || dryRun;
|
||||
final var uuid = reportDirectory.getFileName().toString();
|
||||
final var tmsHost = context.tmsProperties().getHost();
|
||||
final var issueKeyPattern = context.tmsProperties().getIssueKeyPattern();
|
||||
final var allureServerBaseUrl = url(context.getAllureProperties());
|
||||
var launchResultsGroupByIssueTags = launchResultsGroupByIssueTags(launchResults, tmsHost, issueKeyPattern);
|
||||
|
||||
log.info("[PLUGIN {}] Statistic\n{}",
|
||||
getName(),
|
||||
launchResultsGroupByIssueTags.entrySet().stream()
|
||||
.map(e ->
|
||||
"%-20s : %s".formatted(
|
||||
e.getKey(),
|
||||
e.getValue().stream()
|
||||
.map(tr -> "name: %s | full-name: %s | status: %s | %s ".formatted(substringBefore(tr.getName(), '\n'), tr.getFullName(), tr.getStatus(), tr.getTime()))
|
||||
.collect(joining("\n" + StringUtils.repeat(' ', 23)))
|
||||
)
|
||||
)
|
||||
.collect(joining("\n"))
|
||||
);
|
||||
|
||||
launchResultsGroupByIssueTags
|
||||
.entrySet()
|
||||
.parallelStream()
|
||||
.forEach(e -> {
|
||||
var issueKey = e.getKey();
|
||||
var results = e.getValue();
|
||||
try {
|
||||
var newScenarioModel = launchResultsToMarkdownStatisticModel(results, allureServerBaseUrl, context.getReportUrl());
|
||||
|
||||
if (newScenarioModel != null) {
|
||||
sendComment(newScenarioModel, issueKey, context, effectiveDryRun);
|
||||
log.info("[PLUGIN {}] Comment with executed scenarios created for issue '{}' for report '{}'\n{}", getName(), issueKey, uuid, newScenarioModel.toMarkdown());
|
||||
} else
|
||||
log.error("[PLUGIN {}] Server Internal Error. Failed to create comment for issue '{}'. Cannot build scenario model for results. Report '{}'", getName(), issueKey, uuid);
|
||||
} catch (Throwable err) {
|
||||
log.error("[PLUGIN %s] Failed to create comment for issue '%s'. Report '%s'".formatted(getName(), issueKey, uuid), err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "YouTrack integration";
|
||||
}
|
||||
|
||||
static void sendComment(MarkdownStatisticModel newScenarioModel, String issueKey, Context context, boolean dryRun) {
|
||||
var client = context.beanFactory().getBean(IssuesClient.class);
|
||||
|
||||
List<IssueCommentDto> issueCommentDtos = !dryRun ? client.issuesIdCommentsGet(issueKey, "id,text,author", null, null) : Collections.emptyList();
|
||||
Optional<IssueCommentDto> commentWithScenarios = issueCommentDtos.stream().filter(it -> it.getText().contains(COMMENT_TITLE)).findFirst();
|
||||
|
||||
final var commentToCreateOrUpdate = new IssueCommentDto();
|
||||
if (commentWithScenarios.isEmpty()) {
|
||||
commentToCreateOrUpdate.setText(newScenarioModel.toMarkdown());
|
||||
if (!dryRun)
|
||||
client.issuesIdCommentsPost(issueKey, null, true, null, commentToCreateOrUpdate);
|
||||
} else {
|
||||
var previousMarkdownText = commentWithScenarios.get().getText();
|
||||
var previousModel = MarkdownStatisticModel.toModel(previousMarkdownText);
|
||||
var mergedModel = previousModel.merge(newScenarioModel);
|
||||
commentToCreateOrUpdate.setText(mergedModel.toMarkdown());
|
||||
if (!dryRun)
|
||||
client.issuesIdCommentsIssueCommentIdPost(issueKey, commentWithScenarios.get().getId(), true, null, commentToCreateOrUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static Map<String, List<TestResult>> launchResultsGroupByIssueTags(Collection<LaunchResults> launchResults, String tmsHost, Pattern issuePattern) {
|
||||
|
||||
return launchResults
|
||||
.stream()
|
||||
.flatMap(lr -> lr.getResults().stream()
|
||||
.flatMap(r -> r.getLinks().stream()
|
||||
.map(link -> Map.entry(link, r))))
|
||||
.filter(e -> isIssueKey(e.getKey(), tmsHost, issuePattern))
|
||||
.map(e -> Map.entry(extractIssueKey(e.getKey(), issuePattern), e.getValue()))
|
||||
.collect(Collectors.groupingBy(
|
||||
Map.Entry::getKey,
|
||||
Collectors.mapping(Map.Entry::getValue, Collectors.toList())
|
||||
));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static MarkdownStatisticModel launchResultsToMarkdownStatisticModel(Collection<TestResult> launchResults, String baseLink, String reportLink) {
|
||||
|
||||
var rows = launchResults.stream()
|
||||
.filter(scenario -> scenario.getStatus() != SKIPPED && scenario.getStatus() != UNKNOWN)
|
||||
.map(scenario ->
|
||||
new MarkdownStatisticModel.Row(
|
||||
substringBefore(scenario.getName(), '\n'),
|
||||
!isPassed(scenario) ? new Statistic(1, LocalDate.now().toString(), join(reportLink, "#suites", scenario.getUid())) : none,
|
||||
isPassed(scenario) ? new Statistic(1, LocalDate.now().toString(), join(reportLink, "#suites", scenario.getUid())) : none
|
||||
)
|
||||
).collect(
|
||||
Collectors.toMap(
|
||||
MarkdownStatisticModel.Row::scenario,
|
||||
it -> it,
|
||||
MarkdownStatisticModel.Row::merge
|
||||
)
|
||||
)
|
||||
.values()
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(MarkdownStatisticModel.Row::scenario))
|
||||
.toList();
|
||||
|
||||
if (!rows.isEmpty())
|
||||
return new MarkdownStatisticModel(
|
||||
COMMENT_TITLE,
|
||||
rows,
|
||||
new Total(rows.size()),
|
||||
new MarkdownStatisticModel.Footer(ALLURE_SERVER, baseLink)
|
||||
);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isPassed(TestResult result) {
|
||||
return result.getStatus() == Status.PASSED || result.getStatus() == SKIPPED;
|
||||
}
|
||||
|
||||
private static boolean isIssueKey(Link allureLink, String tmsHost, Pattern issuePattern) {
|
||||
var isTms = StringUtils.substringAfter(allureLink.getUrl(), "//").startsWith(tmsHost);
|
||||
var hasKey = issuePattern.matcher(allureLink.getUrl()).find() || issuePattern.matcher(allureLink.getName()).find();
|
||||
return isTms && hasKey;
|
||||
}
|
||||
|
||||
private static String extractIssueKey(Link allureLink, Pattern issuePattern) {
|
||||
Matcher matcher = issuePattern.matcher(allureLink.getName());
|
||||
if (matcher.find())
|
||||
return matcher.group(0);
|
||||
|
||||
matcher = Pattern.compile("(" + issuePattern.pattern() + ")").matcher(allureLink.getUrl());
|
||||
if (matcher.find())
|
||||
return matcher.group(1);
|
||||
|
||||
throw new IllegalArgumentException("Failed to extract issue key from link: " + allureLink);
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
package ru.iopump.qa.allure.helper.plugin.youtrack;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public record MarkdownStatisticModel(
|
||||
String title,
|
||||
List<Row> scenarioStatisticRows,
|
||||
Total total,
|
||||
Footer footer
|
||||
) {
|
||||
|
||||
public String toMarkdown() {
|
||||
return """
|
||||
### %s
|
||||
|
||||
| **Scenario** | ❌ `Failed` | ✅ `Passed` |
|
||||
|--------------|-------------|--------------|
|
||||
%s
|
||||
|
||||
%s
|
||||
|
||||
%s
|
||||
""".formatted(
|
||||
title,
|
||||
scenarioStatisticRows.stream().sorted(comparing(Row::scenario)).map(Row::toMarkdown).collect(joining("\n")),
|
||||
total.toMarkdown(),
|
||||
footer.toMarkdown());
|
||||
}
|
||||
|
||||
public static MarkdownStatisticModel toModel(String text) {
|
||||
var lines = text.lines()
|
||||
.filter(it -> !it.isBlank())
|
||||
.toList();
|
||||
var linesIterator = lines.iterator();
|
||||
|
||||
var title = linesIterator.hasNext() ? StringUtils.substringAfter(linesIterator.next(), "###").trim() : "parsing_error";
|
||||
if (linesIterator.hasNext()) linesIterator.next();
|
||||
if (linesIterator.hasNext()) linesIterator.next();
|
||||
var rows = new ArrayList<Row>();
|
||||
String rowText = null;
|
||||
while (linesIterator.hasNext()) {
|
||||
rowText = linesIterator.next();
|
||||
var newRow = Row.toModel(rowText);
|
||||
if (newRow == Row.error)
|
||||
break;
|
||||
rows.add(newRow);
|
||||
rowText = null;
|
||||
}
|
||||
var total = rowText != null ? Total.toModel(rowText) : Total.error;
|
||||
var footer = linesIterator.hasNext() ? Footer.toModel(linesIterator.next()) : Footer.error;
|
||||
|
||||
var model = new MarkdownStatisticModel(title, rows, total, footer);
|
||||
|
||||
return model.hasError()
|
||||
? new MarkdownStatisticModel(title, rows, total, new Footer(footer.generatedBy + " **`with errors check logs`**", footer.link))
|
||||
: model;
|
||||
}
|
||||
|
||||
public MarkdownStatisticModel merge(MarkdownStatisticModel other) {
|
||||
var rows = this.mergeRows(other.scenarioStatisticRows);
|
||||
return new MarkdownStatisticModel(
|
||||
this.title,
|
||||
this.mergeRows(other.scenarioStatisticRows),
|
||||
new Total(rows.size()),
|
||||
this.footer.merge(other.footer)
|
||||
);
|
||||
}
|
||||
|
||||
private List<Row> mergeRows(List<Row> other) {
|
||||
return Stream.concat(this.scenarioStatisticRows.stream(), other.stream())
|
||||
.collect(toMap(
|
||||
it -> it.scenario,
|
||||
it -> it,
|
||||
Row::merge,
|
||||
LinkedHashMap::new))
|
||||
.values()
|
||||
.stream()
|
||||
.sorted(comparing(Row::scenario))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return scenarioStatisticRows.stream().anyMatch(Row::hasError) || total.hasError() || footer.hasError();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public record Row(
|
||||
String scenario,
|
||||
Statistic passed,
|
||||
Statistic failed
|
||||
) {
|
||||
private static final Row error = new Row("parsing_error", Statistic.error, Statistic.error);
|
||||
private static final Pattern pattern = Pattern.compile("\\| (.+?) \\| (.+?) \\| (.+?) \\|");
|
||||
private static final String md_template = "| %s | %s | %s |";
|
||||
|
||||
public String toMarkdown() {
|
||||
return String.format(md_template, scenario, passed.toMarkdown(), failed.toMarkdown());
|
||||
}
|
||||
|
||||
public static Row toModel(String text) {
|
||||
var matcher = pattern.matcher(text);
|
||||
|
||||
if (matcher.find())
|
||||
return new Row(
|
||||
matcher.group(1).trim(),
|
||||
Statistic.toModel(matcher.group(2).trim()),
|
||||
Statistic.toModel(matcher.group(3).trim())
|
||||
);
|
||||
else
|
||||
return error;
|
||||
}
|
||||
|
||||
public Row merge(Row other) {
|
||||
if (other == error) return this;
|
||||
return this == error ? other : new Row(
|
||||
this.scenario,
|
||||
this.passed.merge(other.passed),
|
||||
this.failed.merge(other.failed)
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return this == error || passed.hasError() || failed.hasError();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public record Statistic(
|
||||
int count,
|
||||
String latestDate,
|
||||
String latestLink
|
||||
) {
|
||||
public static final Statistic none = new Statistic(0, "", "");
|
||||
static final Statistic error = new Statistic(0, "parsing_error", "parsing_error");
|
||||
private static final Pattern pattern = Pattern.compile("\\*\\*(\\d+)\\*\\* times \\[`latest` on (.+)]\\((.+)\\)");
|
||||
private static final String md_template = "**%d** times [`latest` on %s](%s)";
|
||||
|
||||
public String toMarkdown() {
|
||||
return String.format(md_template, count, latestDate, latestLink);
|
||||
}
|
||||
|
||||
public static Statistic toModel(String text) {
|
||||
var matcher = pattern.matcher(text);
|
||||
|
||||
if (matcher.find())
|
||||
return new Statistic(
|
||||
Integer.parseInt(matcher.group(1).trim()),
|
||||
matcher.group(2).trim(),
|
||||
matcher.group(3).trim()
|
||||
);
|
||||
else
|
||||
return error;
|
||||
}
|
||||
|
||||
public Statistic merge(Statistic other) {
|
||||
if (other == error || other.count <= 0) return this;
|
||||
return this == error ? other : new Statistic(
|
||||
this.count + other.count,
|
||||
isNotBlank(other.latestDate) ? other.latestDate : this.latestDate,
|
||||
isNotBlank(other.latestLink) ? other.latestLink : this.latestLink
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return this == error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public record Total(int total) {
|
||||
private static final Total error = new Total(-1);
|
||||
private static final Pattern pattern = Pattern.compile("Total test scenarios: \\*\\*(\\d+)\\*\\*");
|
||||
private static final String md_template = "Total test scenarios: **%d**";
|
||||
|
||||
public String toMarkdown() {
|
||||
return String.format(md_template, total);
|
||||
}
|
||||
|
||||
public static Total toModel(String text) {
|
||||
var matcher = pattern.matcher(text);
|
||||
|
||||
if (matcher.find())
|
||||
return new Total(Integer.parseInt(matcher.group(1).trim()));
|
||||
else
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return this == error;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public record Footer(String generatedBy, String link) {
|
||||
private static final Footer error = new Footer("parsing_error", "parsing_error");
|
||||
private static final Pattern pattern = Pattern.compile("_Generated by \\[(.+)]\\((.+)\\)_");
|
||||
private static final String md_template = "_Generated by [%s](%s)_";
|
||||
|
||||
public String toMarkdown() {
|
||||
return String.format(md_template, generatedBy, link);
|
||||
}
|
||||
|
||||
public static Footer toModel(String text) {
|
||||
var matcher = pattern.matcher(text);
|
||||
|
||||
if (matcher.find())
|
||||
return new Footer(
|
||||
matcher.group(1),
|
||||
matcher.group(2)
|
||||
);
|
||||
else
|
||||
return error;
|
||||
}
|
||||
|
||||
public Footer merge(Footer other) {
|
||||
return new Footer(
|
||||
other.generatedBy,
|
||||
other.link
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return this == error;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
### E2E testing scenarios
|
||||
|
||||
| **Scenario** | ❌ `Failed` | ✅ `Passed` |
|
||||
|--------------|--------------------------------------------|--------------------------------------------|
|
||||
| Data 1 | **2** times [`latest` on 01.01.2024](link) | **3** times [`latest` on 01.01.2024](link) |
|
||||
| Data 5 | **6** times [`latest` on 01.01.2023](link) | **7** times [`latest` on 01.01.2023](link) |
|
||||
|
||||
Total test scenarios: **2**
|
||||
|
||||
_Generated by [Allure Report Plugin - YouTrack Plugin](link)_
|
@ -1,18 +1,19 @@
|
||||
package ru.iopump.qa.allure.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import ru.iopump.qa.allure.service.PathUtil;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ReportGenerateRequest {
|
||||
|
@ -3,7 +3,7 @@ package ru.iopump.qa.allure.model;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.google.common.base.Joiner;
|
||||
import io.qameta.allure.entity.ExecutorInfo;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
package ru.iopump.qa.allure.properties;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@ -16,29 +18,32 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
@ConfigurationProperties(prefix = "allure")
|
||||
@Getter
|
||||
@Accessors(fluent = true)
|
||||
@ConstructorBinding
|
||||
@Slf4j
|
||||
@ToString
|
||||
public class AllureProperties {
|
||||
|
||||
private final Reports reports;
|
||||
private final String resultsDir;
|
||||
private final boolean supportOldFormat;
|
||||
private final String dateFormat;
|
||||
private final String serverBaseUrl;
|
||||
@Nullable
|
||||
private final Resource logo;
|
||||
private final String title;
|
||||
|
||||
public AllureProperties(Reports reports, String resultsDir, boolean supportOldFormat, String dateFormat, String serverBaseUrl) {
|
||||
@ConstructorBinding
|
||||
public AllureProperties(Reports reports, String resultsDir, String dateFormat, String serverBaseUrl, @Nullable Resource logo, String title) {
|
||||
this.reports = defaultIfNull(reports, new Reports());
|
||||
this.resultsDir = defaultIfNull(resultsDir, "allure/results/");
|
||||
this.supportOldFormat = defaultIfNull(supportOldFormat, false);
|
||||
this.dateFormat = defaultIfNull(dateFormat, "yy/MM/dd HH:mm:ss");
|
||||
this.serverBaseUrl = defaultIfNull(serverBaseUrl, null);
|
||||
this.logo = logo;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
if (log.isInfoEnabled())
|
||||
log.info("[ALLURE SERVER CONFIGURATION] Main AllureProperties parameters: " + this);
|
||||
log.info("[ALLURE SERVER CONFIGURATION] Main AllureProperties parameters: {}", this);
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
@ -1,20 +1,19 @@
|
||||
package ru.iopump.qa.allure.properties;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
|
||||
@ConfigurationProperties(prefix = "basic.auth")
|
||||
@Getter
|
||||
@Accessors(fluent = true)
|
||||
@ConstructorBinding
|
||||
|
||||
@Slf4j
|
||||
@ToString(exclude = "password")
|
||||
public class BasicProperties {
|
||||
@ -23,6 +22,7 @@ public class BasicProperties {
|
||||
private final String password;
|
||||
private final boolean enable;
|
||||
|
||||
@ConstructorBinding
|
||||
public BasicProperties(String username, String password, boolean enable) {
|
||||
this.username = defaultIfNull(username, "admin");
|
||||
this.password = defaultIfNull(password, "admin");
|
||||
|
@ -3,7 +3,7 @@ package ru.iopump.qa.allure.properties;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
@ -0,0 +1,19 @@
|
||||
package ru.iopump.qa.allure.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "tms")
|
||||
public class TmsProperties {
|
||||
|
||||
private final boolean enabled;
|
||||
private final String host;
|
||||
private final String apiBaseUrl;
|
||||
private final String project;
|
||||
private final String token;
|
||||
private final Pattern issueKeyPattern;
|
||||
private final boolean dryRun;
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package ru.iopump.qa.allure.security;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import ru.iopump.qa.allure.properties.BasicProperties;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@Slf4j
|
||||
public class BasicConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final BasicProperties basicProperties;
|
||||
private final boolean enableOAuth2;
|
||||
private final boolean enableBasicAuth;
|
||||
private final boolean enableAnyAuth;
|
||||
|
||||
BasicConfiguration(BasicProperties basicProperties, @Value("${app.security.enable-oauth2:false}") boolean enableOAuth2) {
|
||||
super();
|
||||
|
||||
this.basicProperties = basicProperties;
|
||||
this.enableBasicAuth = basicProperties.enable();
|
||||
this.enableOAuth2 = enableOAuth2;
|
||||
this.enableAnyAuth = enableBasicAuth || enableOAuth2;
|
||||
|
||||
log.info("[ALLURE SERVER SECURITY] Basic Auth: {} | OAuth2: {}", enableBasicAuth, enableOAuth2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
if (enableBasicAuth)
|
||||
auth.inMemoryAuthentication()
|
||||
.withUser(basicProperties.username())
|
||||
.password(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode(basicProperties.password()))
|
||||
.roles("USER", "ADMIN");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.headers().frameOptions().sameOrigin()
|
||||
.and()
|
||||
.csrf().disable()
|
||||
.requestCache().requestCache(new CustomRequestCache());
|
||||
|
||||
if (enableAnyAuth)
|
||||
http
|
||||
.authorizeRequests()
|
||||
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
|
||||
.anyRequest().authenticated();
|
||||
|
||||
if (enableOAuth2)
|
||||
http
|
||||
.oauth2Login();
|
||||
|
||||
if (enableBasicAuth)
|
||||
http
|
||||
.httpBasic();
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package ru.iopump.qa.allure.security;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||
|
||||
class CustomRequestCache extends HttpSessionRequestCache {
|
||||
|
@ -0,0 +1,74 @@
|
||||
package ru.iopump.qa.allure.security;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import ru.iopump.qa.allure.properties.BasicProperties;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@Slf4j
|
||||
public class SecurityConfiguration {
|
||||
|
||||
private final BasicProperties basicProperties;
|
||||
private final boolean enableOAuth2;
|
||||
private final boolean enableBasicAuth;
|
||||
private final boolean enableAnyAuth;
|
||||
|
||||
public SecurityConfiguration(BasicProperties basicProperties, @Value("${app.security.enable-oauth2:false}") boolean enableOAuth2) {
|
||||
this.basicProperties = basicProperties;
|
||||
this.enableBasicAuth = basicProperties.enable();
|
||||
this.enableOAuth2 = enableOAuth2;
|
||||
this.enableAnyAuth = enableBasicAuth || enableOAuth2;
|
||||
|
||||
log.info("[ALLURE SERVER SECURITY] Basic Auth: {} | OAuth2: {}", enableBasicAuth, enableOAuth2);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.headers(it -> it.frameOptions(FrameOptionsConfig::sameOrigin))
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.requestCache(it -> it.requestCache(new CustomRequestCache()));
|
||||
|
||||
if (enableAnyAuth)
|
||||
http
|
||||
.authorizeHttpRequests(it -> it
|
||||
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
|
||||
.anyRequest().authenticated());
|
||||
|
||||
if (enableOAuth2)
|
||||
http
|
||||
.oauth2Login(withDefaults());
|
||||
|
||||
if (enableBasicAuth)
|
||||
http
|
||||
.httpBasic(withDefaults());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
UserDetails user = User.withUsername(basicProperties.username())
|
||||
.password(encoder.encode(basicProperties.password()))
|
||||
.roles("USER", "ADMIN")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
}
|
@ -2,9 +2,9 @@ package ru.iopump.qa.allure.security;
|
||||
|
||||
import com.vaadin.flow.server.HandlerHelper;
|
||||
import com.vaadin.flow.shared.ApplicationConstants;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@UtilityClass
|
||||
|
@ -3,6 +3,7 @@ package ru.iopump.qa.allure.service;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@ -16,11 +17,13 @@ import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
import ru.iopump.qa.allure.properties.CleanUpProperties;
|
||||
import ru.iopump.qa.allure.repo.JpaReportRepository;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
import java.time.*;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -41,15 +44,15 @@ public class CleanUpServiceConfiguration implements SchedulingConfigurer {
|
||||
|
||||
private static String print(Collection<Pair<ReportEntity, Boolean>> removedReports) {
|
||||
return removedReports.stream().map(pair ->
|
||||
format("CleanUpResult(id=%s, path=%s, create=%s, age=%sd, isDeleted=%s)",
|
||||
pair.getKey().getUuid(),
|
||||
pair.getKey().getPath(),
|
||||
pair.getKey().getCreatedDateTime(),
|
||||
Duration.between(
|
||||
pair.getKey().getCreatedDateTime(),
|
||||
LocalDateTime.now()
|
||||
).toDays(),
|
||||
pair.getValue())
|
||||
format("CleanUpResult(id=%s, path=%s, create=%s, age=%sd, isDeleted=%s)",
|
||||
pair.getKey().getUuid(),
|
||||
pair.getKey().getPath(),
|
||||
pair.getKey().getCreatedDateTime(),
|
||||
Duration.between(
|
||||
pair.getKey().getCreatedDateTime(),
|
||||
LocalDateTime.now()
|
||||
).toDays(),
|
||||
pair.getValue())
|
||||
).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@ -69,63 +72,63 @@ public class CleanUpServiceConfiguration implements SchedulingConfigurer {
|
||||
}
|
||||
|
||||
final Collection<ReportEntity> candidatesCleanUp = repository
|
||||
.findAllByCreatedDateTimeIsBefore(cleanUpProperties.getClosestEdgeDate());
|
||||
.findAllByCreatedDateTimeIsBefore(cleanUpProperties.getClosestEdgeDate());
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("CleanUp. All reports: " + repository.findAll().stream()
|
||||
.map(e -> e.getUuid() + " " + e.getPath() + " " + e.getCreatedDateTime())
|
||||
.collect(Collectors.joining(", ", "[", "]"))
|
||||
.map(e -> e.getUuid() + " " + e.getPath() + " " + e.getCreatedDateTime())
|
||||
.collect(Collectors.joining(", ", "[", "]"))
|
||||
);
|
||||
|
||||
log.debug("CleanUp. Candidates to clean up: " + candidatesCleanUp.stream()
|
||||
.map(e -> e.getUuid() + " " + e.getPath() + " " + e.getCreatedDateTime())
|
||||
.collect(Collectors.joining(", ", "[", "]"))
|
||||
.map(e -> e.getUuid() + " " + e.getPath() + " " + e.getCreatedDateTime())
|
||||
.collect(Collectors.joining(", ", "[", "]"))
|
||||
);
|
||||
}
|
||||
|
||||
final Collection<Pair<ReportEntity, Boolean>> processedReports = candidatesCleanUp.stream()
|
||||
.map(report ->
|
||||
cleanUpProperties.getPaths().stream()
|
||||
// Есть ли среди настроек paths для данного отчета
|
||||
.filter(path -> report.getPath().equals(path.getPath())).findFirst()
|
||||
// Если отчет подпадает под правила paths, то найти правило и использовать
|
||||
.map(path -> {
|
||||
if (report.getCreatedDateTime().isBefore(path.getEdgeDate()))
|
||||
// Если отчет создан до крайней даты, то удалять
|
||||
return delete(report);
|
||||
else
|
||||
// Оставить если младше
|
||||
return Pair.of(report, false);
|
||||
})
|
||||
// Если отчет не подпадает под правила paths, то использовать общее правило ageDays
|
||||
.orElseGet(() -> {
|
||||
if (report.getCreatedDateTime().isBefore(cleanUpProperties.getEdgeDate()))
|
||||
// Если отчет создан до крайней даты, то удалять
|
||||
return delete(report);
|
||||
else
|
||||
// Оставить если младше
|
||||
return Pair.of(report, false);
|
||||
})
|
||||
).collect(Collectors.toUnmodifiableList());
|
||||
.map(report ->
|
||||
cleanUpProperties.getPaths().stream()
|
||||
// Есть ли среди настроек paths для данного отчета
|
||||
.filter(path -> report.getPath().equals(path.getPath())).findFirst()
|
||||
// Если отчет подпадает под правила paths, то найти правило и использовать
|
||||
.map(path -> {
|
||||
if (report.getCreatedDateTime().isBefore(path.getEdgeDate()))
|
||||
// Если отчет создан до крайней даты, то удалять
|
||||
return delete(report);
|
||||
else
|
||||
// Оставить если младше
|
||||
return Pair.of(report, false);
|
||||
})
|
||||
// Если отчет не подпадает под правила paths, то использовать общее правило ageDays
|
||||
.orElseGet(() -> {
|
||||
if (report.getCreatedDateTime().isBefore(cleanUpProperties.getEdgeDate()))
|
||||
// Если отчет создан до крайней даты, то удалять
|
||||
return delete(report);
|
||||
else
|
||||
// Оставить если младше
|
||||
return Pair.of(report, false);
|
||||
})
|
||||
).toList();
|
||||
|
||||
if (log.isInfoEnabled()) log.info("CleanUp finished with results: " + print(processedReports));
|
||||
|
||||
}, triggerContext -> {
|
||||
|
||||
final LocalDate nextDate = Optional.ofNullable(triggerContext.lastScheduledExecutionTime())
|
||||
// Если триггер уже срабатывал, то прибавить день
|
||||
.map(date -> date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().plusDays(1))
|
||||
// Если триггер не срабатывал, то взять текущий день
|
||||
.orElse(LocalDate.now());
|
||||
// Если триггер уже срабатывал, то прибавить день
|
||||
.map(date -> date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().plusDays(1))
|
||||
// Если триггер не срабатывал, то взять текущий день
|
||||
.orElse(LocalDate.now());
|
||||
|
||||
final LocalTime nextTime = cleanUpProperties.getTime();
|
||||
|
||||
// Следующее срабатывание из даты и времени из настроек
|
||||
final LocalDateTime nextDateTime = LocalDateTime.of(nextDate, nextTime);
|
||||
|
||||
if (log.isInfoEnabled()) log.info("Next CleanUp scheduled at " + nextDateTime);
|
||||
if (log.isInfoEnabled()) log.info("Next CleanUp scheduled at {}", nextDateTime);
|
||||
|
||||
return Date.from(nextDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
return nextDateTime.atZone(ZoneId.systemDefault()).toInstant();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,10 @@ import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.qameta.allure.entity.ExecutorInfo;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
@ -16,15 +20,10 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.iopump.qa.allure.entity.ReportEntity;
|
||||
import ru.iopump.qa.allure.helper.AllureReportGenerator;
|
||||
import ru.iopump.qa.allure.helper.OldReportsFormatConverterHelper;
|
||||
import ru.iopump.qa.allure.helper.ServeRedirectHelper;
|
||||
import ru.iopump.qa.allure.properties.AllureProperties;
|
||||
import ru.iopump.qa.allure.repo.JpaReportRepository;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.transaction.Transactional;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
@ -78,7 +77,7 @@ public class JpaReportService {
|
||||
@PostConstruct
|
||||
protected void initRedirection() {
|
||||
repository.findByActiveTrue().forEach(
|
||||
e -> redirection.mapRequestTo(join(cfg.reports().path(), e.getPath()), reportsDir.resolve(e.getUuid().toString()).toString())
|
||||
e -> redirection.mapRequestTo(join(cfg.reports().path(), e.getPath()), reportsDir.resolve(e.getUuid().toString()).toString())
|
||||
);
|
||||
}
|
||||
|
||||
@ -88,11 +87,11 @@ public class JpaReportService {
|
||||
|
||||
// delete active history
|
||||
entitiesActive
|
||||
.forEach(e -> deleteQuietly(reportsDir.resolve(e.getUuid().toString()).resolve("history").toFile()));
|
||||
.forEach(e -> deleteQuietly(reportsDir.resolve(e.getUuid().toString()).resolve("history").toFile()));
|
||||
|
||||
// delete active history
|
||||
entitiesInactive
|
||||
.forEach(e -> deleteQuietly(reportsDir.resolve(e.getUuid().toString()).toFile()));
|
||||
.forEach(e -> deleteQuietly(reportsDir.resolve(e.getUuid().toString()).toFile()));
|
||||
|
||||
return entitiesInactive;
|
||||
}
|
||||
@ -132,45 +131,45 @@ public class JpaReportService {
|
||||
final Path destination = reportUnzipService.unzipAndStore(archiveInputStream);
|
||||
final UUID uuid = UUID.fromString(destination.getFileName().toString());
|
||||
Preconditions.checkArgument(
|
||||
Files.list(destination).anyMatch(path -> path.endsWith("index.html")),
|
||||
"Uploaded archive is not an Allure Report"
|
||||
Files.list(destination).anyMatch(path -> path.endsWith("index.html")),
|
||||
"Uploaded archive is not an Allure Report"
|
||||
);
|
||||
|
||||
// Find prev report if present
|
||||
final Optional<ReportEntity> prevEntity = repository.findByPathOrderByCreatedDateTimeDesc(reportPath)
|
||||
.stream()
|
||||
.findFirst();
|
||||
.stream()
|
||||
.findFirst();
|
||||
|
||||
// Add CI executor information
|
||||
var safeExecutorInfo = addExecutionInfo(
|
||||
destination,
|
||||
executorInfo,
|
||||
baseUrl + str(reportsDir.resolve(uuid.toString())) + "/index.html",
|
||||
uuid
|
||||
destination,
|
||||
executorInfo,
|
||||
baseUrl + str(reportsDir.resolve(uuid.toString())) + "/index.html",
|
||||
uuid
|
||||
);
|
||||
|
||||
log.info("Report '{}' loaded", destination);
|
||||
|
||||
// New report entity
|
||||
final ReportEntity newEntity = ReportEntity.builder()
|
||||
.uuid(uuid)
|
||||
.path(reportPath)
|
||||
.createdDateTime(LocalDateTime.now(zeroZone()))
|
||||
.url(join(baseUrl, cfg.reports().dir(), uuid.toString()) + "/")
|
||||
.level(prevEntity.map(e -> e.getLevel() + 1).orElse(0L))
|
||||
.active(true)
|
||||
.size(ReportEntity.sizeKB(destination))
|
||||
.buildUrl(
|
||||
// Взять Build Url
|
||||
ofNullable(safeExecutorInfo.getBuildUrl())
|
||||
// Or Build Name
|
||||
.or(() -> ofNullable(safeExecutorInfo.getBuildName()))
|
||||
// Or Executor Name
|
||||
.or(() -> ofNullable(safeExecutorInfo.getName()))
|
||||
// Or Executor Type
|
||||
.orElse(safeExecutorInfo.getType())
|
||||
)
|
||||
.build();
|
||||
.uuid(uuid)
|
||||
.path(reportPath)
|
||||
.createdDateTime(LocalDateTime.now(zeroZone()))
|
||||
.url(join(baseUrl, cfg.reports().dir(), uuid.toString()) + "/")
|
||||
.level(prevEntity.map(e -> e.getLevel() + 1).orElse(0L))
|
||||
.active(true)
|
||||
.size(ReportEntity.sizeKB(destination))
|
||||
.buildUrl(
|
||||
// Взять Build Url
|
||||
ofNullable(safeExecutorInfo.getBuildUrl())
|
||||
// Or Build Name
|
||||
.or(() -> ofNullable(safeExecutorInfo.getBuildName()))
|
||||
// Or Executor Name
|
||||
.or(() -> ofNullable(safeExecutorInfo.getName()))
|
||||
// Or Executor Type
|
||||
.orElse(safeExecutorInfo.getType())
|
||||
)
|
||||
.build();
|
||||
|
||||
// Add request mapping
|
||||
redirection.mapRequestTo(newEntity.getPath(), reportsDir.resolve(uuid.toString()).toString());
|
||||
@ -191,11 +190,6 @@ public class JpaReportService {
|
||||
@Nullable ExecutorInfo executorInfo,
|
||||
String baseUrl
|
||||
) throws IOException {
|
||||
if (cfg.supportOldFormat() && init.compareAndSet(false, true)) {
|
||||
var old = new OldReportsFormatConverterHelper(cfg).convertOldFormat();
|
||||
repository.saveAll(old);
|
||||
old.forEach(e -> redirection.mapRequestTo(e.getPath(), reportsDir.resolve(e.getUuid().toString()).toString()));
|
||||
}
|
||||
// Preconditions
|
||||
Preconditions.checkArgument(!resultDirs.isEmpty());
|
||||
resultDirs.forEach(i -> Preconditions.checkArgument(Files.exists(i), "Result '%s' doesn't exist", i));
|
||||
@ -205,33 +199,34 @@ public class JpaReportService {
|
||||
|
||||
// Find prev report if present
|
||||
final Optional<ReportEntity> prevEntity = repository.findByPathOrderByCreatedDateTimeDesc(reportPath)
|
||||
.stream()
|
||||
.findFirst();
|
||||
.stream()
|
||||
.findFirst();
|
||||
|
||||
// New uuid directory
|
||||
final Path destination = reportsDir.resolve(uuid.toString());
|
||||
|
||||
// Copy history from prev report
|
||||
final Optional<Path> historyO = prevEntity
|
||||
.flatMap(e -> copyHistory(reportsDir.resolve(e.getUuid().toString()), uuid.toString()))
|
||||
.or(Optional::empty);
|
||||
.flatMap(e -> copyHistory(reportsDir.resolve(e.getUuid().toString()), uuid.toString()))
|
||||
.or(Optional::empty);
|
||||
|
||||
// Add CI executor information
|
||||
var safeExecutorInfo = addExecutionInfo(
|
||||
resultDirs.get(0),
|
||||
executorInfo,
|
||||
baseUrl + str(reportsDir.resolve(uuid.toString())) + "/index.html",
|
||||
uuid
|
||||
resultDirs.get(0),
|
||||
executorInfo,
|
||||
baseUrl + str(reportsDir.resolve(uuid.toString())) + "/index.html",
|
||||
uuid
|
||||
);
|
||||
|
||||
var reportUrl = join(baseUrl, cfg.reports().dir(), uuid.toString()) + "/";
|
||||
try {
|
||||
// Add history to results if exists
|
||||
final List<Path> resultDirsToGenerate = historyO
|
||||
.map(history -> (List<Path>) ImmutableList.<Path>builder().addAll(resultDirs).add(history).build())
|
||||
.orElse(resultDirs);
|
||||
.map(history -> (List<Path>) ImmutableList.<Path>builder().addAll(resultDirs).add(history).build())
|
||||
.orElse(resultDirs);
|
||||
|
||||
// Generate new report with history
|
||||
reportGenerator.generate(destination, resultDirsToGenerate);
|
||||
reportGenerator.generate(destination, resultDirsToGenerate, reportUrl);
|
||||
|
||||
log.info("Report '{}' generated according to results '{}'", destination, resultDirsToGenerate);
|
||||
} finally {
|
||||
@ -245,24 +240,24 @@ public class JpaReportService {
|
||||
|
||||
// New report entity
|
||||
final ReportEntity newEntity = ReportEntity.builder()
|
||||
.uuid(uuid)
|
||||
.path(reportPath)
|
||||
.createdDateTime(LocalDateTime.now(zeroZone()))
|
||||
.url(join(baseUrl, cfg.reports().dir(), uuid.toString()) + "/")
|
||||
.level(prevEntity.map(e -> e.getLevel() + 1).orElse(0L))
|
||||
.active(true)
|
||||
.size(ReportEntity.sizeKB(destination))
|
||||
.buildUrl(
|
||||
// Взять Build Url
|
||||
ofNullable(safeExecutorInfo.getBuildUrl())
|
||||
// Or Build Name
|
||||
.or(() -> ofNullable(safeExecutorInfo.getBuildName()))
|
||||
// Or Executor Name
|
||||
.or(() -> ofNullable(safeExecutorInfo.getName()))
|
||||
// Or Executor Type
|
||||
.orElse(safeExecutorInfo.getType())
|
||||
)
|
||||
.build();
|
||||
.uuid(uuid)
|
||||
.path(reportPath)
|
||||
.createdDateTime(LocalDateTime.now(zeroZone()))
|
||||
.url(reportUrl)
|
||||
.level(prevEntity.map(e -> e.getLevel() + 1).orElse(0L))
|
||||
.active(true)
|
||||
.size(ReportEntity.sizeKB(destination))
|
||||
.buildUrl(
|
||||
// Взять Build Url
|
||||
ofNullable(safeExecutorInfo.getBuildUrl())
|
||||
// Or Build Name
|
||||
.or(() -> ofNullable(safeExecutorInfo.getBuildName()))
|
||||
// Or Executor Name
|
||||
.or(() -> ofNullable(safeExecutorInfo.getName()))
|
||||
// Or Executor Type
|
||||
.orElse(safeExecutorInfo.getType())
|
||||
)
|
||||
.build();
|
||||
|
||||
// Add request mapping
|
||||
redirection.mapRequestTo(newEntity.getPath(), reportsDir.resolve(uuid.toString()).toString());
|
||||
@ -290,17 +285,17 @@ public class JpaReportService {
|
||||
// If size more than max history
|
||||
if (allReports.size() >= max) {
|
||||
log.info("Current report count '{}' exceed max history report count '{}'",
|
||||
allReports.size(),
|
||||
max
|
||||
allReports.size(),
|
||||
max
|
||||
);
|
||||
|
||||
// Delete last after max history
|
||||
long deleted = allReports.stream()
|
||||
.skip(max)
|
||||
.peek(e -> log.info("Report '{}' will be deleted", e))
|
||||
.peek(e -> deleteQuietly(reportsDir.resolve(e.getUuid().toString()).toFile()))
|
||||
.peek(repository::delete)
|
||||
.count();
|
||||
.skip(max)
|
||||
.peek(e -> log.info("Report '{}' will be deleted", e))
|
||||
.peek(e -> deleteQuietly(reportsDir.resolve(e.getUuid().toString()).toFile()))
|
||||
.peek(repository::delete)
|
||||
.count();
|
||||
|
||||
// Update level (safety)
|
||||
created.setLevel(Math.max(created.getLevel() - deleted, 0));
|
||||
|
@ -1,7 +1,8 @@
|
||||
package ru.iopump.qa.allure.service;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@SuppressWarnings("RedundantModifiersUtilityClassLombok")
|
||||
|
@ -1,25 +0,0 @@
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/iron-iconset-svg/iron-iconset-svg.js';
|
||||
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
const template = html`<iron-iconset-svg name="icomoon" size="1024">
|
||||
<svg>
|
||||
<defs>
|
||||
<g id="linkedin">
|
||||
<path fill="#0077b5" style="fill: var(--color1, #0077b5)" class="path1"
|
||||
d="M872.405 872.619h-151.637v-237.611c0-56.661-1.152-129.579-79.019-129.579-79.061 0-91.136 61.653-91.136 125.397v241.792h-151.637v-488.619h145.664v66.603h1.963c20.352-38.4 69.845-78.933 143.787-78.933 153.643 0 182.059 101.12 182.059 232.747zM227.712 317.141c-48.811 0-88.021-39.509-88.021-88.107 0-48.555 39.253-88.021 88.021-88.021 48.64 0 88.064 39.467 88.064 88.021 0 48.597-39.467 88.107-88.064 88.107zM303.744 872.619h-152.064v-488.619h152.064zM948.267 0h-872.704c-41.771 0-75.563 33.024-75.563 73.771v876.459c0 40.789 33.792 73.771 75.563 73.771h872.576c41.728 0 75.861-32.981 75.861-73.771v-876.459c0-40.747-34.133-73.771-75.861-73.771z"></path>
|
||||
</g>
|
||||
<g id="github">
|
||||
<path class="path1"
|
||||
d="M512 12.672c-282.88 0-512 229.248-512 512 0 226.261 146.688 418.133 350.080 485.76 25.6 4.821 34.987-11.008 34.987-24.619 0-12.16-0.427-44.373-0.64-87.040-142.421 30.891-172.459-68.693-172.459-68.693-23.296-59.093-56.96-74.88-56.96-74.88-46.379-31.744 3.584-31.104 3.584-31.104 51.413 3.584 78.421 52.736 78.421 52.736 45.653 78.293 119.851 55.68 149.12 42.581 4.608-33.109 17.792-55.68 32.427-68.48-113.707-12.8-233.216-56.832-233.216-253.013 0-55.893 19.84-101.547 52.693-137.387-5.76-12.928-23.040-64.981 4.48-135.509 0 0 42.88-13.739 140.8 52.48 40.96-11.392 84.48-17.024 128-17.28 43.52 0.256 87.040 5.888 128 17.28 97.28-66.219 140.16-52.48 140.16-52.48 27.52 70.528 10.24 122.581 5.12 135.509 32.64 35.84 52.48 81.493 52.48 137.387 0 196.693-119.68 240-233.6 252.587 17.92 15.36 34.56 46.763 34.56 94.72 0 68.523-0.64 123.563-0.64 140.203 0 13.44 8.96 29.44 35.2 24.32 204.843-67.157 351.403-259.157 351.403-485.077 0-282.752-229.248-512-512-512z"></path>
|
||||
</g>
|
||||
<g id="docker">
|
||||
<path fill="#1488c6" style="fill: var(--color2, #1488c6)" class="path1"
|
||||
d="M205.653 737.067c-29.184 0-55.637-23.893-55.637-52.907s23.893-53.035 55.68-53.035c31.915 0 55.893 23.893 55.893 52.992s-26.539 52.907-55.936 52.949zM888.832 448.512c-5.76-42.325-32-76.8-66.56-103.253l-13.44-10.667-10.837 13.227c-21.077 23.893-29.44 66.261-26.88 97.92 2.56 23.979 10.24 47.787 23.637 66.304-10.837 5.547-24.235 10.667-34.56 16.085-24.32 7.979-47.957 10.667-71.68 10.667h-684.373l-2.56 15.787c-5.12 50.432 2.56 103.253 23.979 151.040l10.411 18.56v2.56c64 105.941 177.92 153.6 301.995 153.6 238.677 0 434.432-103.253 527.232-325.675 60.8 2.645 122.197-13.227 151.040-71.509l7.68-13.227-12.8-7.979c-34.56-21.077-81.92-23.893-121.6-13.227zM547.157 406.187h-103.595v103.253h103.68v-103.339zM547.157 276.352h-103.595v103.253h103.68v-103.125zM547.157 143.915h-103.595v103.253h103.68v-103.253zM673.877 406.187h-102.997v103.253h103.253v-103.339zM289.963 406.187h-102.955v103.253h103.339v-103.339zM419.243 406.187h-102.4v103.253h102.997v-103.339zM161.963 406.187h-102.229v103.253h103.595v-103.339zM419.243 276.352h-102.4v103.253h102.997v-103.125zM289.323 276.352h-102.144v103.253h102.955v-103.125z"></path>
|
||||
</g>
|
||||
</defs>
|
||||
</svg>
|
||||
</iron-iconset-svg>`;
|
||||
|
||||
document.head.appendChild(template.content);
|
@ -16,18 +16,27 @@ spring:
|
||||
database: H2
|
||||
show-sql: false
|
||||
hibernate.ddl-auto: update
|
||||
cloud:
|
||||
openfeign:
|
||||
client:
|
||||
config:
|
||||
default:
|
||||
loggerLevel: basic
|
||||
|
||||
vaadin.urlMapping: "/ui/*"
|
||||
server.port: ${PORT:8080}
|
||||
|
||||
### Security
|
||||
basic.auth:
|
||||
username: admin
|
||||
password: admin
|
||||
enable: false
|
||||
basic:
|
||||
auth:
|
||||
username: admin
|
||||
password: admin
|
||||
enable: false
|
||||
|
||||
### App Configuration
|
||||
springdoc.swagger-ui.path: /swagger-ui.html
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
|
||||
# #################
|
||||
# Deprecated format. Not supported!
|
||||
@ -40,8 +49,11 @@ springdoc.swagger-ui.path: /swagger-ui.html
|
||||
# history.level: 20
|
||||
# support.old.format: false
|
||||
# date.format: "yy/MM/dd HH:mm:ss"
|
||||
|
||||
allure:
|
||||
title: "BrewCode | Allure Report"
|
||||
# FROM URL: https://avatars.githubusercontent.com/u/16944358?v=4
|
||||
# FROM FILE: file:/images/logo.png
|
||||
logo: ""
|
||||
resultsDir: allure/results/
|
||||
reports:
|
||||
dir: allure/reports/
|
||||
@ -64,4 +76,13 @@ logging:
|
||||
org.springframework: INFO
|
||||
org.springframework.core: WARN
|
||||
org.springframework.beans.factory.support: WARN
|
||||
ru.iopump.qa:allure: INFO # Allure Server Logs
|
||||
ru.iopump.qa.allure: INFO # Allure Server Logs
|
||||
ru.iopump.qa.allure.api: DEBUG
|
||||
|
||||
tms:
|
||||
enabled: false
|
||||
host: tms.localhost
|
||||
api-base-url: https://${tms.host}/api
|
||||
token: "my-token"
|
||||
issue-key-pattern: "[A-Za-z]+-\\d+"
|
||||
dry-run: false
|
||||
|
1
src/main/resources/icons/docker.svg
Normal file
1
src/main/resources/icons/docker.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M349.9 236.3h-66.1v-59.4h66.1v59.4zm0-204.3h-66.1v60.7h66.1V32zm78.2 144.8H362v59.4h66.1v-59.4zm-156.3-72.1h-66.1v60.1h66.1v-60.1zm78.1 0h-66.1v60.1h66.1v-60.1zm276.8 100c-14.4-9.7-47.6-13.2-73.1-8.4-3.3-24-16.7-44.9-41.1-63.7l-14-9.3-9.3 14c-18.4 27.8-23.4 73.6-3.7 103.8-8.7 4.7-25.8 11.1-48.4 10.7H2.4c-8.7 50.8 5.8 116.8 44 162.1 37.1 43.9 92.7 66.2 165.4 66.2 157.4 0 273.9-72.5 328.4-204.2 21.4 .4 67.6 .1 91.3-45.2 1.5-2.5 6.6-13.2 8.5-17.1l-13.3-8.9zm-511.1-27.9h-66v59.4h66.1v-59.4zm78.1 0h-66.1v59.4h66.1v-59.4zm78.1 0h-66.1v59.4h66.1v-59.4zm-78.1-72.1h-66.1v60.1h66.1v-60.1z"/></svg>
|
After Width: | Height: | Size: 816 B |
1
src/main/resources/icons/github.svg
Normal file
1
src/main/resources/icons/github.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
src/main/resources/icons/linkedin.svg
Normal file
1
src/main/resources/icons/linkedin.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/></svg>
|
After Width: | Height: | Size: 655 B |
1
src/main/resources/loader.properties
Normal file
1
src/main/resources/loader.properties
Normal file
@ -0,0 +1 @@
|
||||
loader.path=build/tmp/jar
|
Binary file not shown.
Binary file not shown.
@ -56,6 +56,20 @@ allure.api.addTranslation('de', {
|
||||
}
|
||||
});
|
||||
|
||||
allure.api.addTranslation('nl', {
|
||||
tab: {
|
||||
behaviors: {
|
||||
name: 'Functionaliteit'
|
||||
}
|
||||
},
|
||||
widget: {
|
||||
behaviors: {
|
||||
name: 'Features en story’s',
|
||||
showAll: 'Toon alle'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
allure.api.addTranslation('he', {
|
||||
tab: {
|
||||
behaviors: {
|
||||
@ -75,10 +89,10 @@ allure.api.addTranslation('br', {
|
||||
behaviors: {
|
||||
name: 'Comportamentos'
|
||||
}
|
||||
},
|
||||
},
|
||||
widget: {
|
||||
behaviors: {
|
||||
name: 'Funcionalidades por história',
|
||||
name: 'Funcionalidades por história',
|
||||
showAll: 'Mostrar tudo'
|
||||
}
|
||||
}
|
||||
@ -126,6 +140,76 @@ allure.api.addTranslation('kr', {
|
||||
}
|
||||
});
|
||||
|
||||
allure.api.addTranslation('fr', {
|
||||
tab: {
|
||||
behaviors: {
|
||||
name: 'Comportements'
|
||||
}
|
||||
},
|
||||
widget: {
|
||||
behaviors: {
|
||||
name: 'Thèmes par histoires',
|
||||
showAll: 'Montrer tout'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
allure.api.addTranslation('pl', {
|
||||
tab: {
|
||||
behaviors: {
|
||||
name: 'Zachowania'
|
||||
}
|
||||
},
|
||||
widget: {
|
||||
behaviors: {
|
||||
name: 'Funkcje według historii',
|
||||
showAll: 'pokaż wszystko'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
allure.api.addTranslation('az', {
|
||||
tab: {
|
||||
behaviors: {
|
||||
name: 'Davranışlar'
|
||||
}
|
||||
},
|
||||
widget: {
|
||||
behaviors: {
|
||||
name: 'Hekayələr üzrə xüsusiyyətlər',
|
||||
showAll: 'hamısını göstər'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
allure.api.addTranslation('sv', {
|
||||
tab: {
|
||||
behaviors: {
|
||||
name: 'Beteenden'
|
||||
}
|
||||
},
|
||||
widget: {
|
||||
behaviors: {
|
||||
name: 'Funktioner efter user stories',
|
||||
showAll: 'visa allt'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
allure.api.addTranslation('isv', {
|
||||
tab: {
|
||||
behaviors: {
|
||||
name: 'Funkcionalnost',
|
||||
}
|
||||
},
|
||||
widget: {
|
||||
behaviors: {
|
||||
name: 'Funkcionalnost',
|
||||
showAll: 'pokaži vsěčto',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
allure.api.addTab('behaviors', {
|
||||
title: 'tab.behaviors.name', icon: 'fa fa-list',
|
||||
route: 'behaviors(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)',
|
||||
@ -147,4 +231,4 @@ allure.api.addWidget('widgets', 'behaviors', allure.components.WidgetStatusView.
|
||||
title: 'widget.behaviors.name',
|
||||
baseUrl: 'behaviors',
|
||||
showLinks: true
|
||||
}));
|
||||
}));
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1,24 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 128 128" version="1.1" viewBox="0 0 128 128" xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"><g id="Layer_1"><rect fill="#F4F5F5" height="1520" opacity="0" width="727.938" x="-59.984" y="-351"/></g>
|
||||
<g id="Layer_2"><g><circle cx="64" cy="64" fill="#6E9583" r="64"/><g><defs><circle cx="64" cy="64" id="SVGID_3_" r="64"/></defs>
|
||||
<clipPath id="SVGID_2_"><use overflow="visible" xlink:href="#SVGID_3_"/></clipPath>
|
||||
<polygon clip-path="url(#SVGID_2_)" fill="#648778" points="93.572,29.677 128,64 128,128 54.36,128 33.341,106.906 "/></g><path
|
||||
d="M84.044,20H36.018C33.579,20,32,22.11,32,24.549v78.903c0,2.439,1.579,4.549,4.018,4.549h55.989 c2.439,0,4.018-2.11,4.018-4.549V32.143L84.044,20z"
|
||||
fill="#F1F1F1"/><g><defs><path d="M84.044,20H36.018C33.579,20,32,22.11,32,24.549v78.903c0,2.439,1.579,4.549,4.018,4.549h55.989 c2.439,0,4.018-2.11,4.018-4.549V32.143L84.044,20z" id="SVGID_5_"/></defs>
|
||||
<clipPath id="SVGID_4_"><use overflow="visible" xlink:href="#SVGID_5_"/></clipPath>
|
||||
<g clip-path="url(#SVGID_4_)"><polygon fill="#DDE1F1" points="50.948,67.621 65.539,82.042 42.971,83.087 49.777,90 42.971,91.087 49.277,97.555 42.971,99.087 53.027,109.305 97.684,109.305 97.684,75.707 97.075,54.055 81.059,37.758 70.97,44.918 62.684,35.107 "/></g></g><path
|
||||
d="M88.186,32.138l7.839,0.005L84.044,20v7.96C84.044,30.398,85.769,32.138,88.186,32.138z" fill="#C2DFC9"/><path
|
||||
d="M84,83.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,83.5,84,83.5z"
|
||||
fill="#495260"/><path
|
||||
d="M84,91.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,91.5,84,91.5z"
|
||||
fill="#495260"/><path
|
||||
d="M84,99.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,99.5,84,99.5z"
|
||||
fill="#495260"/><g><path d="M69.568,31.844l-1.319,11.303c2.314,0.88,4.242,2.728,5.132,5.245c0.573,1.619,0.631,3.292,0.274,4.851 l10.257,4.895c0.527,0.252,1.155-0.023,1.329-0.581c1.308-4.188,1.323-8.819-0.253-13.273 c-2.379-6.723-7.827-11.477-14.212-13.254C70.21,30.872,69.636,31.26,69.568,31.844z" fill="#0E9CD9"/>
|
||||
<path d="M66.68,59.901c-3.653,0.668-7.398-1.12-9.176-4.38c-1.094-2.006-1.312-4.174-0.858-6.157L46.39,44.469 c-0.527-0.251-1.155,0.023-1.329,0.58c-1.286,4.118-1.322,8.663,0.175,13.049c3.701,10.842,15.624,16.783,26.503,13.191 c4.655-1.537,8.399-4.531,10.911-8.3c0.324-0.486,0.141-1.147-0.385-1.398l-10.257-4.896 C70.751,58.296,68.929,59.49,66.68,59.901z"
|
||||
fill="#E95037"/>
|
||||
<path d="M62.239,43.074c0.734-0.26,1.479-0.405,2.22-0.464l1.316-11.275c0.067-0.576-0.389-1.08-0.968-1.071 c-2.218,0.035-4.469,0.421-6.676,1.202c-4.455,1.576-8.045,4.5-10.479,8.151c-0.324,0.486-0.142,1.147,0.385,1.399l10.257,4.895 C59.282,44.654,60.62,43.647,62.239,43.074z"
|
||||
fill="#69B32D"/>
|
||||
<g><defs><path d="M69.695,30.76l-1.446,12.387c2.314,0.88,4.242,2.728,5.132,5.245c0.573,1.619,0.631,3.292,0.274,4.851 l10.257,4.895c0.527,0.252,1.155-0.023,1.329-0.581c1.308-4.188,1.323-8.819-0.253-13.273 C82.476,37.185,76.541,32.281,69.695,30.76z M66.68,59.901c-3.653,0.668-7.398-1.12-9.176-4.38 c-1.094-2.006-1.312-4.174-0.858-6.157L46.39,44.469c-0.527-0.251-1.155,0.023-1.329,0.58 c-1.286,4.118-1.322,8.663,0.175,13.049c3.701,10.842,15.624,16.783,26.503,13.191c4.655-1.537,8.399-4.531,10.911-8.3 c0.324-0.486,0.141-1.147-0.385-1.398l-10.257-4.896C70.751,58.296,68.929,59.49,66.68,59.901z M62.239,43.074 c0.734-0.26,1.479-0.405,2.22-0.464l1.316-11.275c0.067-0.576-0.389-1.08-0.968-1.071c-2.218,0.035-4.469,0.421-6.676,1.202 c-4.455,1.576-8.045,4.5-10.479,8.151c-0.324,0.486-0.142,1.147,0.385,1.399l10.257,4.895 C59.282,44.654,60.62,43.647,62.239,43.074z" id="SVGID_7_"/></defs>
|
||||
<clipPath id="SVGID_6_"><use overflow="visible" xlink:href="#SVGID_7_"/></clipPath>
|
||||
<circle clip-path="url(#SVGID_6_)" cx="65.151" cy="51.304" fill="#FFFFFF" opacity="0.4" r="12.507"/></g></g></g></g></svg>
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 128 128" version="1.1" viewBox="0 0 128 128" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1"><rect fill="#F4F5F5" height="1520" opacity="0" width="727.938" x="-59.984" y="-351"/></g><g id="Layer_2"><g><circle cx="64" cy="64" fill="#6E9583" r="64"/><g><defs><circle cx="64" cy="64" id="SVGID_3_" r="64"/></defs><clipPath id="SVGID_2_"><use overflow="visible" xlink:href="#SVGID_3_"/></clipPath><polygon clip-path="url(#SVGID_2_)" fill="#648778" points="93.572,29.677 128,64 128,128 54.36,128 33.341,106.906 "/></g><path d="M84.044,20H36.018C33.579,20,32,22.11,32,24.549v78.903c0,2.439,1.579,4.549,4.018,4.549h55.989 c2.439,0,4.018-2.11,4.018-4.549V32.143L84.044,20z" fill="#F1F1F1"/><g><defs><path d="M84.044,20H36.018C33.579,20,32,22.11,32,24.549v78.903c0,2.439,1.579,4.549,4.018,4.549h55.989 c2.439,0,4.018-2.11,4.018-4.549V32.143L84.044,20z" id="SVGID_5_"/></defs><clipPath id="SVGID_4_"><use overflow="visible" xlink:href="#SVGID_5_"/></clipPath><g clip-path="url(#SVGID_4_)"><polygon fill="#DDE1F1" points="50.948,67.621 65.539,82.042 42.971,83.087 49.777,90 42.971,91.087 49.277,97.555 42.971,99.087 53.027,109.305 97.684,109.305 97.684,75.707 97.075,54.055 81.059,37.758 70.97,44.918 62.684,35.107 "/></g></g><path d="M88.186,32.138l7.839,0.005L84.044,20v7.96C84.044,30.398,85.769,32.138,88.186,32.138z" fill="#C2DFC9"/><path d="M84,83.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,83.5,84,83.5z" fill="#495260"/><path d="M84,91.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,91.5,84,91.5z" fill="#495260"/><path d="M84,99.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,99.5,84,99.5z" fill="#495260"/><g><path d="M69.568,31.844l-1.319,11.303c2.314,0.88,4.242,2.728,5.132,5.245c0.573,1.619,0.631,3.292,0.274,4.851 l10.257,4.895c0.527,0.252,1.155-0.023,1.329-0.581c1.308-4.188,1.323-8.819-0.253-13.273 c-2.379-6.723-7.827-11.477-14.212-13.254C70.21,30.872,69.636,31.26,69.568,31.844z" fill="#0E9CD9"/><path d="M66.68,59.901c-3.653,0.668-7.398-1.12-9.176-4.38c-1.094-2.006-1.312-4.174-0.858-6.157L46.39,44.469 c-0.527-0.251-1.155,0.023-1.329,0.58c-1.286,4.118-1.322,8.663,0.175,13.049c3.701,10.842,15.624,16.783,26.503,13.191 c4.655-1.537,8.399-4.531,10.911-8.3c0.324-0.486,0.141-1.147-0.385-1.398l-10.257-4.896 C70.751,58.296,68.929,59.49,66.68,59.901z" fill="#E95037"/><path d="M62.239,43.074c0.734-0.26,1.479-0.405,2.22-0.464l1.316-11.275c0.067-0.576-0.389-1.08-0.968-1.071 c-2.218,0.035-4.469,0.421-6.676,1.202c-4.455,1.576-8.045,4.5-10.479,8.151c-0.324,0.486-0.142,1.147,0.385,1.399l10.257,4.895 C59.282,44.654,60.62,43.647,62.239,43.074z" fill="#69B32D"/><g><defs><path d="M69.695,30.76l-1.446,12.387c2.314,0.88,4.242,2.728,5.132,5.245c0.573,1.619,0.631,3.292,0.274,4.851 l10.257,4.895c0.527,0.252,1.155-0.023,1.329-0.581c1.308-4.188,1.323-8.819-0.253-13.273 C82.476,37.185,76.541,32.281,69.695,30.76z M66.68,59.901c-3.653,0.668-7.398-1.12-9.176-4.38 c-1.094-2.006-1.312-4.174-0.858-6.157L46.39,44.469c-0.527-0.251-1.155,0.023-1.329,0.58 c-1.286,4.118-1.322,8.663,0.175,13.049c3.701,10.842,15.624,16.783,26.503,13.191c4.655-1.537,8.399-4.531,10.911-8.3 c0.324-0.486,0.141-1.147-0.385-1.398l-10.257-4.896C70.751,58.296,68.929,59.49,66.68,59.901z M62.239,43.074 c0.734-0.26,1.479-0.405,2.22-0.464l1.316-11.275c0.067-0.576-0.389-1.08-0.968-1.071c-2.218,0.035-4.469,0.421-6.676,1.202 c-4.455,1.576-8.045,4.5-10.479,8.151c-0.324,0.486-0.142,1.147,0.385,1.399l10.257,4.895 C59.282,44.654,60.62,43.647,62.239,43.074z" id="SVGID_7_"/></defs><clipPath id="SVGID_6_"><use overflow="visible" xlink:href="#SVGID_7_"/></clipPath><circle clip-path="url(#SVGID_6_)" cx="65.151" cy="51.304" fill="#FFFFFF" opacity="0.4" r="12.507"/></g></g></g></g></svg>
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.0 KiB |
@ -1,4 +1,4 @@
|
||||
.side-nav__brand {
|
||||
background: url('custom-logo.svg') no-repeat left center !important;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
BIN
src/main/resources/plugins/jira-plugin/jira-plugin-2.29.0.jar
Normal file
BIN
src/main/resources/plugins/jira-plugin/jira-plugin-2.29.0.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/plugins/jira-plugin/lib/annotations-13.0.jar
Normal file
BIN
src/main/resources/plugins/jira-plugin/lib/annotations-13.0.jar
Normal file
Binary file not shown.
BIN
src/main/resources/plugins/jira-plugin/lib/byte-buddy-1.14.9.jar
Normal file
BIN
src/main/resources/plugins/jira-plugin/lib/byte-buddy-1.14.9.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/plugins/jira-plugin/lib/okhttp-4.12.0.jar
Normal file
BIN
src/main/resources/plugins/jira-plugin/lib/okhttp-4.12.0.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/plugins/jira-plugin/lib/okio-jvm-3.6.0.jar
Normal file
BIN
src/main/resources/plugins/jira-plugin/lib/okio-jvm-3.6.0.jar
Normal file
Binary file not shown.
BIN
src/main/resources/plugins/jira-plugin/lib/retrofit-2.11.0.jar
Normal file
BIN
src/main/resources/plugins/jira-plugin/lib/retrofit-2.11.0.jar
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user