DEV Community

Cover image for Building an AI Fix SonarQube Dashboard with Vaadin and Spring Boot
Jonathan Vila
Jonathan Vila

Posted on

Building an AI Fix SonarQube Dashboard with Vaadin and Spring Boot

SonarQube provides developers with static code analysis capabilities, allowing teams to identify and resolve code quality issues. It also includes an AI Fix feature that helps developers fix specific issues using the full power of AI.

The project sonarqube-bulk-ai-fix shows how to focus only on those issues with AI fixes as an example of how to easily connect to SonarQube Server API and show the fixes in SonarQube IDE.

In this article, we’ll explore how the project leverages the SonarQube API alongside Vaadin and Spring Boot to create an intuitive dashboard for AI fixing code issues.

Why SonarQube API?

SonarQube’s API provides extensive capabilities to interact with its core features programmatically, enabling:

  • Fetching project and issue data
  • Managing quality gates and rules
  • Integrating with CI/CD pipelines

This project highlights the use of the API to identify issues and seamlessly process AI fixes. Let's examine the technical components.


Overview of the Architecture

The project architecture integrates:

  1. Spring Boot: Acts as the backend to manage business logic and handle API interactions.
  2. Vaadin: Provides a modern, web-based UI for the dashboard.
  3. SonarQube API: Fetches project issues and applies fixes programmatically.

Prerequisites

  1. SonarQube Server / SonarQube Cloud instance running with AI CodeFix feature enabled (> 10.7)
  2. IDE running (VSCode, IntelliJ)
  3. Container runtime (Podman, Docker)

Running SonarQube Server

If you don't have access to a SonarQube Cloud instance with CodeFix feature enabled, you can try this example locally. To do that,request an Enterprise free trial license.

The next step is to create a docker-compose file to have persistence in our analysis, otherwise, it would use a memory database, and every time you run the container, it will be empty.

version: "3"

services:
  sonarqube:
    image: sonarqube:enterprise
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
      - ~/cacerts:/opt/java/openjdk/lib/security/cacerts
    ports:
      - "9000:9000"
  db:
    image: postgres:latest
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:
  postgresql_data:
Enter fullscreen mode Exit fullscreen mode

And finally, run SonarQube with this command :

docker-compose -f ./docker-compose-sonarqube-postgre.yaml up

After a few seconds, you can open our browser on localhost:9000 and specify the new password (the default is admin/admin).
Now you should paste the contents of the trial license file we received and Voila!

Analysing our first project

In order to test the connection with real data, you would need to incorporate a project analysis in the SonarQube dashboard. To do this for a given Java project simply run this command in the project folder (considering it's a Maven Java application):

mvn clean verify sonar:sonar -Dsonar.projectKey=sample -Dsonar.host.url=http://localhost:9000 -Dsonar.login=admin -Dsonar.password={replace with your pass}

Image description

Using the SonarQube API

The first step is connecting to the SonarQube API to retrieve and manipulate issue data. In the project, a SonarQubeClient class encapsulates this functionality.
There are some API calls supported by the Sonar SDK, and others that are direct calls using HttpClient.

To find out which API endpoints and types are supported, go to Web API and Web API v2.

Sonar API Client in Spring Boot

You can use the sonar-ws SDK to connect to SonarQube and get the issues for a given filter.
We need to use pagination, as it's only returning 100 elements per query.

    public SearchWsResponse getListOfIssues(String project, String page, String pageSize) {
        // Call the SonarQube API to get the issues
        var httpConnector = HttpConnector.newBuilder()
                .url(filter.sonarqubeUrl())
                .credentials(filter.sonarqubeUser(), filter.sonarqubePassword())
                .build();
        var wsClient = WsClientFactories.getDefault().newClient(httpConnector);
        var issueRequest = new SearchRequest();
        issueRequest.setProjects(Collections.singletonList(project));
        if (filter.severity() != null) {
            issueRequest.setSeverities(List.of(filter.severity()));
        }

        issueRequest.setP(page);
        issueRequest.setPs(pageSize);
        return wsClient.issues().search(issueRequest);
    }
Enter fullscreen mode Exit fullscreen mode

To check if an issue has an AI Code Fix, you need to make a direct call using the HttpRequest object because the SDK doesn't yet cover this call.

// check if the issue has code fix suggestions
HttpRequest requestCheckIfIssueHasCodeFix = HttpRequest.newBuilder()
    .uri(URI.create(urlFixSuggestionsIssues))
    .header("Authorization", getAuthorization())
    .build();

HttpResponse<String> response = client.send(requestCheckIfIssueHasCodeFix, BodyHandlers.ofString());
if (response.body().contains("\"aiSuggestion\":\"AVAILABLE\"")) {
  issuesWithCodeFix.add(issue);
}
Enter fullscreen mode Exit fullscreen mode

Interacting with the IDE

One feature of this Dashboard is the ability to send the AI Fix to the local running instance of an IDE and commit the change to our code base.

SonarQube for IDE has an API you can use to send pieces of code that need to be refactored.

public void sendCodeFixToSonarLint(int port, Issue issue, AISuggestion issueCodeFix, String codeFile)
            throws JsonProcessingException {
        var uri = URI.create("http://localhost:" + port + SONARLINT_API_FIX +
                "?server=" + URLEncoder.encode(filter.sonarqubeUrl(), StandardCharsets.UTF_8) +
                "&project=" + filter.project() +
                "&issue=" + issueCodeFix.issueId() +
                "&branch=master");

        var codeFileLines = codeFile.split("\n");
        var sonarLintSuggestion = new SonarLintSuggestion(
                issueCodeFix.explanation(),
                new SonarLintSuggestion.FileEdit(
                        issueCodeFix.changes().stream().map(change -> new SonarLintSuggestion.FileEdit.Change(
                                change.newCode(),
                                String.join("\n",
                                        Arrays.copyOfRange(codeFileLines, change.startLine() - 1, change.endLine())),
                                new SonarLintSuggestion.FileEdit.Change.LineRange(
                                        change.startLine(),
                                        change.endLine())))
                                .toList(),
                        getFileFromComponent(issue.getComponent())),
                issueCodeFix.id());
        HttpRequest sendCodeFixToSonarLintRequest = HttpRequest.newBuilder()
                .uri(URI.create(uri.toString()))
                .POST(BodyPublishers.ofString(new ObjectMapper().writeValueAsString(sonarLintSuggestion)))
                .build();

        HttpClient client = HttpClient.newHttpClient();
        try {
            HttpResponse<String> response = client.send(sendCodeFixToSonarLintRequest, BodyHandlers.ofString());
            System.out.println(response.request().toString() + "\n" + response.body());

            writeToFileAppliedCodeFix(issueCodeFix);
            Notification.show("Response from SonarLint: " + response.statusCode() + "\n" + response.body());
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
Enter fullscreen mode Exit fullscreen mode

Image description


Building the UI with Vaadin

Vaadin simplifies the process of building web UIs in Java by combining a component-based framework with server-side logic.

The process is to create different components and add them to layout containers.

Here’s how the project creates a dashboard page:

Vaadin Page for the dashboard

Adding the components to the layouts :

public IssuesDashboard() {
  sonarqubePanel = new HorizontalLayout();
  sonarqubeUrlEdit = new TextField("SonarQube Server URL");
  sonarqubeUrlEdit.setValue("http://localhost:9000");

  sonarqubeUserEdit = new TextField("User");
  sonarqubePasswordEdit = new TextField("Password");

  sonarqubePanel.add(sonarqubeUrlEdit, sonarqubeUserEdit, sonarqubePasswordEdit);
}
Enter fullscreen mode Exit fullscreen mode

Listeners to button actions :

applyFixesButton = new Button("Send Selected Fix to SonarQube IDE");
applyFixesButton.addClickListener(e -> {
   Notification.show("Processing fixes");
   applyFixes();
   Notification.show("Processing fixes finished");
});
Enter fullscreen mode Exit fullscreen mode

Bringing It All Together

To run the project:

  1. Start the Spring Boot application:

    ./mvnw spring-boot:run
    
  2. Access the Vaadin UI at http://localhost:8080.

You can then enter the project key, fetch issues, and explore the automation possibilities.


Conclusion

The sonarqube-bulk-ai-fix project demonstrates the power of combining the SonarQube API, Vaadin, and Spring Boot to create a user-friendly and efficient dashboard for AI code fixes. Whether you're looking to streamline your workflows or explore innovative ways to interact with SonarQube, this project provides an excellent start point.

Check out the full source code and contribute to the project on GitHub.

Top comments (0)