Skip to content

Commit

Permalink
Feature/GitHub actions (#1)
Browse files Browse the repository at this point in the history
add github actions and sonarqube integration
  • Loading branch information
balrok authored Oct 15, 2023
1 parent aa9bce6 commit 481dbb6
Show file tree
Hide file tree
Showing 21 changed files with 584 additions and 25 deletions.
55 changes: 55 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
name: Build

on: # yamllint disable-line rule:truthy
push:
branches:
- "master"
- "main"
- "develop"
paths-ignore:
- '.gitignore'
- 'CODEOWNERS'
- 'LICENSE'
- '*.md'
- '*.adoc'
- '*.txt'
- '.all-contributorsrc'
pull_request:
paths-ignore:
- '.gitignore'
- 'CODEOWNERS'
- 'LICENSE'
- '*.md'
- '*.adoc'
- '*.txt'
- '.all-contributorsrc'

jobs:
build:
permissions:
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Cache SonarQube packages
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew build sonar --no-daemon
- name: Build native
run: ./gradlew build --no-daemon -Dquarkus.package.type=native
42 changes: 42 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
name: release-build

on: # yamllint disable-line rule:truthy
release:
types: [created]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Install graalvm
uses: DeLaGuardo/setup-graalvm@3
with:
graalvm-version: '20.0.0.java11'
- name: Install native-image
run: gu install native-image
- name: Build native executable
run: "./gradlew -Pversion=\"${{ github.event.release.tag_name }}\" build -Dquarkus.package.type=native"
- name: Upload native executable
id: upload-native-executable
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./build/qualityannotate-${{ github.event.release.tag_name }}-runner
asset_name: qualityannotate-${{ github.event.release.tag_name }}-linux
asset_content_type: application/octet-stream
- name: Upload config
id: upload-config
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./build/resources/main/application.yml
asset_name: application.yml
asset_content_type: text/yaml
7 changes: 7 additions & 0 deletions .yamllint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
extends: default

rules:
line-length:
max: 120
level: warning
37 changes: 30 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
plugins {
id 'java'
id "jacoco"
id 'io.quarkus'
id("io.freefair.lombok") version "8.4"
id "org.sonarqube" version "4.3.1.3277"
}

repositories {
Expand All @@ -24,18 +26,39 @@ group 'org.acme'
version '1.0.0-SNAPSHOT'

java {
sourceCompatibility = JavaVersion.VERSION_16
targetCompatibility = JavaVersion.VERSION_16
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
compileJava {
options.encoding = 'UTF-8'
options.compilerArgs << '-parameters'
}

compileTestJava {
options.encoding = 'UTF-8'
}
}

test {
systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager"
}
compileJava {
options.encoding = 'UTF-8'
options.compilerArgs << '-parameters'

project.tasks["sonar"].dependsOn "build"

sonar {
properties {
property "sonar.projectKey", "quyt_qualityannotate"
property "sonar.organization", "quyt"
property "sonar.host.url", "https://sonarcloud.io"

property "sonar.projectName", "Quality Annotate"
property "sonar.sourceEncoding", "UTF-8"
}
}

compileTestJava {
options.encoding = 'UTF-8'
jacocoTestReport {
reports {
xml.required = true
html.outputLocation = layout.buildDirectory.dir('jacocoHtml')
}
}
test.finalizedBy jacocoTestReport
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
5 changes: 5 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
10 changes: 10 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sonar.host.url=https://sonarcloud.io
sonar.projectName=Quality Annotate
sonar.projectKey=qualityannotate
sonar.sourceEncoding=UTF-8


sonar.sources=src/main/java/
sonar.java.libraries=target/*.jar
sonar.java.binaries=target/classes/org/acme/rest/json
sonar.java.test.binaries=target/test-classes/org/acme/rest/json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.qualityannotate.api.qualitytool;

public record Issue(String fileName, String lineNumber, String comment, String severity) {
public record Issue(String fileName, Integer lineNumber, String comment, String severity) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithName;

import java.util.List;

@ConfigMapping(prefix = "sonarqube")
public interface SonarqubeConfig {
@WithName("url")
Expand All @@ -16,4 +18,7 @@ public interface SonarqubeConfig {

@WithName("pull_request")
String pullRequest();

@WithName("global_metric_types")
List<String> globalMetricTypes();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.qualityannotate.core.rest.BasicAuthRequestFilter;
import org.qualityannotate.quality.sonarqube.client.ComponentMeasures;
import org.qualityannotate.quality.sonarqube.client.Measure;
import org.qualityannotate.quality.sonarqube.client.Metric;
import org.qualityannotate.quality.sonarqube.client.SonarqubeApiClient;
import org.qualityannotate.quality.sonarqube.client.*;
import org.qualityannotate.api.qualitytool.GlobalMetrics;
import org.qualityannotate.api.qualitytool.Issue;
import org.qualityannotate.api.qualitytool.QualityTool;
Expand All @@ -31,16 +28,34 @@ public SonarqubeQualityTool(SonarqubeConfig config) {

@Override
public List<Issue> getIssues() {
return Collections.emptyList();
IssueSearch issuesSearch = client.getIssuesSearch(config.project(), config.pullRequest(), null, null, null, null, null, null, null, null, null);
// TODO issueSearch.facets can be used for globalMetrics - but maybe doesn't matter
List<Issue> issues = new ArrayList<>();
for (SqIssue sqIssue : issuesSearch.issues()) {
issues.add(new Issue(sqIssue.getPath(config.project()), sqIssue.getLineNumber(), sqIssue.getMessage(), sqIssue.getSeverity()));
}
return issues;
}

@Override
public GlobalMetrics getGlobalMetrics() {
ComponentMeasures componentMeasures = client.getComponentMeasures(config.project(), config.pullRequest(), "new_coverage,new_sqale_debt_ratio,new_uncovered_conditions");
ComponentMeasures componentMeasures = client.getComponentMeasures(config.project(), config.pullRequest(),
String.join(",", config.globalMetricTypes()));
Map<String, String> metrics = new HashMap<>();
for (Metric metric : componentMeasures.getMetrics()) {
Optional<Measure> measure = componentMeasures.getComponent().getMeasure(metric.getKey());
measure.ifPresent(value -> metrics.put(metric.getName(), value.getPeriod() == null ? value.getValue() : value.getPeriod().value()));
Optional<Measure> measureOpt = componentMeasures.getComponent().getMeasure(metric.getKey());
if (measureOpt.isPresent()) {
Measure measure = measureOpt.get();
String value = measure.getPeriod() == null ? measure.getValue() : measure.getPeriod().value();
if (metric.getKey().endsWith("coverage")) {
try {
value = String.format("%.2f%%", Double.parseDouble(value));
} catch (NumberFormatException e) {
// ignore
}
}
metrics.put(metric.getName(), value);
}
}
return new GlobalMetrics(metrics);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package org.qualityannotate.quality.sonarqube.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Value;

import java.util.List;
import java.util.Optional;

@Value
@JsonIgnoreProperties(ignoreUnknown = true)
public class Component {
/**
* "key": "MY_PROJECT:ElementImpl.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package org.qualityannotate.quality.sonarqube.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.util.List;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ComponentMeasures {
@JsonProperty("component")
public Component component;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.qualityannotate.quality.sonarqube.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import java.util.List;

public record Facet(List<FacetValue> values) {

/**
* @param val human readable name of the Facet
* @param count some metric count
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record FacetValue(String val, String count) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.qualityannotate.quality.sonarqube.client;

import java.util.List;

public record IssueSearch(Paging paging, List<SqIssue> issues, List<Component> components, List<Facet> facets) {
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package org.qualityannotate.quality.sonarqube.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Value;

@Value
@JsonIgnoreProperties(ignoreUnknown = true)
public class Measure {
/**
* "metric": "complexity",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.qualityannotate.quality.sonarqube.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record Paging(int pageIndex, int pageSize, int total) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.qualityannotate.quality.sonarqube.client;


import jakarta.annotation.Nullable;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
Expand All @@ -10,15 +12,48 @@
@Path("/api")
public interface SonarqubeApiClient {
/**
* <a href="https://sonarqube.inria.fr/sonarqube/web_api/api/measures?internal=true">docs</a>
* <a href="https://sonarqube.inria.fr/sonarqube/web_api/api/measures">docs</a>
*
* @param project Component key. E.g. my_project
* @param prId Pull request id. Not available in the community edition. E.g. 1234
* @param metricKeys Comma-separated list of additional fields that can be returned in the response.
* E.g."new_coverage,new_sqale_debt_ratio,new_uncovered_conditions"
* @return
*/
@GET()
@Path("/measures/component")
ComponentMeasures getComponentMeasures(@QueryParam("component") String project, @QueryParam("pullRequest") String prId, @QueryParam("metricsKeys") String metricKeys);


/**
* <a href="https://sonarqube.inria.fr/sonarqube/web_api/api/issues/search">docs</a>
* TODO onComponentOnly might be interesting to retrieve global metrics
*
* @param project Component key. E.g. my_project
* @param prId Pull request id. Not available in the community edition. E.g. 1234
* @param branch BBranch key. Not available in the community edition. E.g. feature/test
* @param additionalFields Comma-separated list of the optional fields to be returned in response.
* @param facets Comma-separated list of the facets to be computed. No facet is computed by default.
* @param pageNumber Page number. E.g. 1
* @param pageSize Page size. Highest number is 500
* @param resolved To match resolved or unresolved issues
* @param sort Sort the issues
* @param severities Comma-separated list of severities
* @param statuses Comma-separated list of statuses
*/
@GET()
@Path("/issues/search")
IssueSearch getIssuesSearch(@QueryParam("componentKeys") String project,
@Nullable @QueryParam("pullRequest") String prId,
@Nullable @QueryParam("branch") String branch,
@Nullable @QueryParam("additionalFields") @DefaultValue("_all") String additionalFields,
@Nullable @QueryParam("facets") @DefaultValue("severities,types") String facets,
@Nullable @QueryParam("p") @DefaultValue("1") String pageNumber,
// 500 is the biggest size
@Nullable @QueryParam("ps") @DefaultValue("500") String pageSize,
@Nullable @QueryParam("resolved") @DefaultValue("false") String resolved,
@Nullable @QueryParam("s") @DefaultValue("FILE_LINE") String sort,
// INFO got removed here
@Nullable @QueryParam("severities") @DefaultValue("MINOR,MAJOR,CRITICAL,BLOCKER") String severities,
@Nullable @QueryParam("statuses") @DefaultValue("OPEN,REOPENED,CONFIRMED") String statuses
);
}
Loading

0 comments on commit 481dbb6

Please sign in to comment.