Pull Request Monitoring

ID: pull-request-monitoring

Gitter GitHub GitHub pull requests Open GitHub issues GitHub Workflow Status (branch) Build Status Contributions Jenkins Plugins Jenkins Plugin installs codecov Codacy Badge


Logo

Pull Request Monitoring

Jenkins plugin to monitor pull requests with a customizable dashboard.
Explore the docs »

Report Bug · Request Feature

At the Jenkins UX SIG Meeting on 28. April 2021, there was a live demo of the first beta version (1.0.3-beta).

Table of Contents

  1. About The Project
  2. Getting Started
  3. Usage
  4. Available Portlets
  5. Demo
  6. Roadmap
  7. Contributing
  8. License
  9. Credits
  10. Contact

About The Project

Many software teams have changed their development processes to lightweight pull requests. Changes to the software are packed into such a pull request, which is then manually reviewed and also automatically built in the CI/CD server. In Jenkins, however, these pull requests are not "first-class citizens"; the results of a pull request are currently simulated via branches.

For developers, this representation is insufficient: instead of having Git diffs, tests, static analysis, etc. as an overview in the overall project, a filtered representation of these results on the changes actually made would be much more helpful.

This plugin offers a possibility to display and aggregate the results (in the form of individual views) of a pull request in a configurable dashboard. Views can only be accessed or displayed if the corresponding plugin fulfils certain requirements and already provides a view.

Built With

Getting Started

Prerequisites

Currently, only multibranch pipelines projects are supported to use this plugin. Therefore, you have to install the corresponding Jenkins plugin Multibranch: Pipeline and connect to own of your SCM Repositories to use the Pull Request Monitoring Jenkins plugin.

Provide a portlet

This plugin relies on other plugins to provide a view that aggregates and provides delta metrics for a pull request.

The code behind

The MonitorPortlet class defines the base class for each portlet that is to be displayed. In order to register the portlet for the plugin, a factory class is required, which must be provided with the annotation @Extension. The factory has to extend the MonitorPortletFactory abstract class, receives the current Run<?,?>, delivers a set of MonitorPortlets and defines a displayedName, which appears as optgroup in the dashboard in the dropdown list of all available portlets and their corresponding factory.

Add portlet

One Instance Of One Portlet

Normally, one plugin delivers one portlet. A minimal example could look as follows:

/**
 *  An example Monitor Portlet implementation with one portlet delivered by factory.
 */
public class DemoPortlet extends MonitorPortlet {
    private final Run<?, ?> build;

    /**
     * Create a new {@link DemoPortlet}.
     *
     * @param run
     *          the {@link Run}
     */
    public DemoPortlet(Run<?, ?> run) {
        this.build = run;
    }

    /**
     * Defines the title of portlet. It will be shown in the 
     * upper left corner of the portlet.
     *
     * @return
     *          the title as string.
     */
    @Override
    public String getTitle() {
        return "Demo Portlet";
    }

    /**
     * Defines the id for the portlet.
     *
     * @return
     *          the id.
     */
    @Override
    public String getId() {
        return "demo-portlet-id";
    }

    /**
     * Defines whether the portlet is shown per default in the user dashboard or not.
     * 
     * @return
     *          true if portlet should be shown, false else.
     */
    @Override
    public boolean isDefault() {
        return true;
    }
    
    /**
     * Defines the preferred width of the portlet. It's 
     * possible to override the default width by user.
     *
     * @return
     *          the width as int. (range from 100 to 1000)
     */
    @Override
    public int getPreferredWidth() {
        return 300;
    }

    /**
     * Defines the preferred height of the portlet. It's 
     * possible to override the default height by user.
     *
     * @return
     *          the height as int. (range from 100 to 1000)
     */
    @Override
    public int getPreferredHeight() {
        return 200;
    }

    /**
     * Defines the icon, which will be shown in the dropdown of 
     * all available portlets in the dashboard.
     *
     * @return
     *          the icon url as {@link java.util.Optional} of string, or an
     *          empty Optional, if a default icon should be added.
     */
    @Override
    public Optional<String> getIconUrl() {
        return Optional.of("</path-to-icon/icon.png>");
    }

    /**
     * Defines a link to a detail view, if its needed. Links the title
     * of the portlet to this url.
     *
     * @return
     *          {@link java.util.Optional} of the url, or an empty Optional,
     *          if no link should be provided by portlet.
     */
    @Override
    public Optional<String> getDetailViewUrl() {
        return Optional.of("<link-to-detail-view>");
    }

    /**
     * Creates a new {@link ExamplePortletFactory}.
     */
    @Extension
    public static class ExamplePortletFactory extends MonitorPortletFactory {
        @Override
        public Collection<MonitorPortlet> getPortlets(Run<?, ?> build) {
            return Collections.singleton(new DemoPortlet(build));
        }

        @Override
        public String getDisplayName() {
            return "Demo Portlet Factory";
        }
    }
}

Null Check for used actions

Since an empty dashboard is always added by default, it is possible that the method getPortlets(Run) will be called (e.g. open the dashboard of actual run) even though the corresponding run may not be finished. It is possible that actions have not yet been added to the run. It is therefore advisable to perform a null check on the actions of the run required by your portlet and return an empty list if necessary. (Example: code-coverage-api)

Multiple Instances Of One Portlet

The factory can also deliver several portlets of one class.

⚠️ Unique portlet ID:

The id must be unique. Please make sure that the id is related to the plugin so that there are no conflicts with other plugins. It is recommended to use the artifact id of the plugin or parts of it as prefix. If several portlets of the same class are created in the factory, it must be ensured that the ID is always unique for each portlet!

Here is an example of a factory that delivers two instances of a class:

/**
 * An example Monitor Portlet implementation with multiple portlets delivered by factory.
 */
public class DemoPortlet extends MonitorPortlet {
    private final Run<?, ?> run;
    private final String id;

    /**
     * Create a new {@link DemoPortlet}.
     *
     * @param run
     *          the {@link Run}
     * @param id
     *          the id.
     */
    public DemoPortlet(Run<?, ?> run, String id) {
        this.run = run;
        this.id = id;
    }

    @Override
    public String getTitle() {
        return "Demo Portlet " + getId();
    }

    @Override
    public String getId() {
        return id;
    }
    
    // other interface methods, see example above. 
    
    /**
     * Creates a new {@link ExamplePortletFactory}.
     */
    @Extension
    public static class ExamplePortletFactory extends MonitorPortletFactory {
        @Override
        public Collection<MonitorPortlet> getPortlets(Run<?, ?> build) {
            List<MonitorPortlet> portlets = new ArrayList<>();
            portlets.add(new DemoPortlet(build, "example-portlet-first"));
            portlets.add(new DemoPortlet(build, "example-portlet-second"));
            return monitors;
        }

        @Override
        public String getDisplayName() {
            return "Demo Portlet Factory";
        }
    }
}

The corresponding jelly file

Each portlet have to have a corresponding monitor.jelly file, which is responsible for the content of the plugins portlet delivered on the dashboard later. Therefore you have to create a new monitor.jelly file in the directory, which corresponds to the MonitorView class.

Example: The code behind is defined in src/main/java/io/jenkins/plugins/sample/DemoPortlet.java. The related sources (e.g. the monitor.jelly file) have to be defined in src/main/resources/io/jenkins/plugins/sample/DemoPortlet/monitory.jelly.

Now the portlet, which can be added later in the dashboard, can be filled individually. Of course, all obligatory functions of the Jelly files, such as JEXL calls, can be used. To do this, please refer to the official Jenkins documentation. A minimal example:

<?jelly escape-by-default='true'?>

<j:jelly xmlns:j="jelly:core">

    <p>Portlet content goes here!</p>

</j:jelly>

Usage Of Monitoring

Introduction

This plugin offers the following monitoring options:

Project Level

The monitoring on the project level is very basic and is limited to the summary of all open pull requests of the associated SCM. From here, you can access the corresponding monitoring dashboards, view the pull request information and navigate to the respective pull request in the repository.

Example Overview

Build Level

The monitoring at build level is the core of the plugin and offers the possibility to observe various delta metrics of a pull request provided by other portlets.

The dashboard is only added to those builds whose branch SCMHead of the parent job is an instance of ChangeRequestSCMHead. Otherwise, the corresponding MonitoringBuildAction will not be added and no dashboard will be provided for the build. For more details, please refer to the logging in the console output of the respective build.

Persistence & Permissions

The configuration, which is set via the Jenkinsfile or via the Dahsboard, is saved per user as a hudson.model.UserProperty and is browser independent. The plugin therefore requires that you are logged in and have the appropriate permissions. Otherwise, the action will not be displayed!

Default Dashboard

💡 Reference build search:

The git-forensics-api plugin allows to find a reference build for a pull request build. It is highly recommended using this plugin and to search for the reference build in the pipeline with discoverGitReferenceBuild() (or configure it via the Jenkins UI). For more information please visit the plugin page or have a look into an example pipeline.

To start with the default dashboard, you have to do nothing. If the run is part of a pull request, the corresponding MonitoringDefaultAction is automatically added to the Run and later available on the sidepanel of the run.

Now you are able to add portlets to the dashboard, change the layout or remove it again. The configuration will be saved for each project per user. If you want to save the configuration permanently, it is best to copy and paste it into the Jenkinsfile and overwrite the default dashboard and use a custom dashboard.

🧩 Default Portlets for Dashboard:

Since version 1.7.0, the portlets can decide whether they should be displayed by default or not in the user dashboard. So when you start with the default dashboard, all available portlets that are marked as default are automatically loaded into your dashboard.

Custom Dashboard

The other way to add a dashboard is to set the configuration of the dashboard via the Jenkinsfile to pre-define your dashboard. It is recommended to add the monitoring at the end of your pipeline, to ensure that other plugins such as static code analysis are performed first and that the actions that may be required are available:

stage ('Pull Request Monitoring - Dashboard Configuration') {
    monitoring (
        '''
        [
            {
                // Minimal usage of one portlet 
                "id": "portlet-id"
            }, 
            {   
                // Feel free to customize the portlets
                "id": "another-portlet-id",
                "width": 200,
                "height": 100,
                "color": "#FF5733"
            }
        ]
        '''
 )
}

Therefore, the monitoring stage expects a JSONArray of JSONObjects. To validate your configured json, you could use a JSON Schema Validator and the corresponding JSON schema used for this plugin. Each JSONObject needs at least the id of the portlet to add. For width and height, the default preferredWidth and preferredHeight of the MonitorPortlet is used. #000000 is used as default color.

The dashboard will be added to your Run and the pre-defined monitor should be available.

🔥 Duplicate or missing portlet IDs:

Duplicates will be removed as well as missing portlet ids. The Run will not fail unless the provided json does not follow the scheme!

If there are unavailable portlets (e.g. corresponding plugin is uninstalled) in stored user configuration, and the dashboard tries to load this portlet, an alert will be displayed with the ids of the unavailable portlets:

Unavailable Portlet

Settings

Under the settings of each Run, various things can be tracked:

  1. Are there changes of the pre-defined dashboard since the last build?

  2. The current activated source of configuration (Default or User-specific).

    • Default means Jenkinsfile if monitoring is provided, else all available default portlets.

    • User-specific means the local changes of dashboard.

  3. Configuration synced with the default one? If needed, you can synchronize the actual configuration with the default one.

  4. The actual configuration

  5. The default configuration.

Prioritising of the different configurations:

By default, all available default portlets are loaded. If there is a user-specific configuration for one dashboard (per project), the user-specific configuration will be loaded per default. If you sync the current configuration with the default one, the user-specific configuration for current dashboard will be deleted, and the default configuration will be loaded. Deletion cannot be undone!

Settings

Available Portlets

If your plugin is not in the list but provides a portlet, feel free to add it with a pull request!

Plugin Number of delivered portlets Portlet ID Default?
Pull Request Monitoring Pull Request Monitoring 2 first-demo-portlet
second-demo-portlet
Warnings Next Generation Warnings Next Generation ≥ 100 depends on tool
Code Coverage API Code Coverage API 1 code-coverage

Demo

For the demo (v1.3.0-beta) I added two recorder (javadoc and pmd) of the Warnings Ng Plugin to the pipeline and added both as default portlet to the monitoring. After the run finished, the portlets will be shown in the dashboard.

Demo

Roadmap

See the open issues for a list of proposed features (and known issues).

Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

Distributed under the MIT License. See LICENSE for more information.

Credits

The following icons, which are used by this plugin

made by Freepik from Flaticon.

Contact

Simon Symhoven - post@simon-symhoven.de

Project Link: https://github.com/jenkinsci/pull-request-monitoring-plugin