Skip to content

Commit

Permalink
Merge pull request #82 from btkelly/individual-3
Browse files Browse the repository at this point in the history
Post individual comments when possible
  • Loading branch information
btkelly committed Jun 1, 2016
2 parents 3257a7a + 31f98c5 commit 9b07f03
Show file tree
Hide file tree
Showing 19 changed files with 638 additions and 98 deletions.
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Gnag <a href="https://travis-ci.org/btkelly/gnag"><img src="https://travis-ci.org/btkelly/gnag.svg" /></a> [![Coverage Status](https://coveralls.io/repos/btkelly/gnag/badge.svg?branch=master&service=github)](https://coveralls.io/github/btkelly/gnag?branch=master) <a href="http://www.detroitlabs.com/"><img src="https://img.shields.io/badge/Sponsor-Detroit%20Labs-000000.svg" /></a> <a href='https://bintray.com/btkelly/maven/gnag-gradle-plugin/_latestVersion'><img src='https://api.bintray.com/packages/btkelly/maven/gnag-gradle-plugin/images/download.svg'></a> [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-gnag-green.svg?style=true)](https://android-arsenal.com/details/1/3128)
A Gradle plugin that helps facilitate Github PR checking and automatic commenting of violations for Android projects.
A Gradle plugin that helps facilitate GitHub PR checking and automatic commenting of violations for Android projects.

## Usage

Gnag is meant to be simple to use and easy to drop in to any Android project. Shown below is the simplest
Gnag setup that will report violations to GitHub. By default this config will report PMD, Findbugs, Checkstyle and
Android Lint to Github.
Android Lint to GitHub.

```groovy
buildscript {
Expand All @@ -28,7 +28,7 @@ gnag {
}
```

This is the simplest way to add automatic PR checking and commenting to your project. The options defined in the Github closure can be overridden by passing command line parameters with the same name to your build. This is helpful when using in conjunction with a CI system to allow automated builds.
This is the simplest way to add automatic PR checking and commenting to your project. The options defined in the `github` closure can be overridden by passing command line parameters with the same name to your build. This is helpful when using in conjunction with a CI system to allow automated builds.

#### Tasks

Expand All @@ -37,7 +37,7 @@ You can use the gnagCheck gradle task to run Gnag locally and generate an HTML r
./gradlew clean gnagCheck
```

You can use the gnagReport task which will first run gnagCheck and then report detected violations to the Github issue specified.
You can use the gnagReport task which will first run gnagCheck and then report detected violations to the GitHub issue specified.
In this example the issue number and authtoken for the comment user are passed as commandline arguments.
```groovy
./gradlew clean gnagReport -PissueNumber=11 -PauthToken=iu2n3iu2nfjknfjk23nfkj23nk
Expand Down Expand Up @@ -87,23 +87,36 @@ gnag {
- ***androidLint*** - block to customize the android lint reporter
- ***enabled*** - set if the android lint reporter should look for a lint report
- ***severity*** - can be 'Error' or 'Warning' depending on which severity you want Gnag to check
- ***github*** - block to customize Github reporting (only used during the `gnagReport` task
- ***github*** - block to customize GitHub reporting (only used during the `gnagReport` task
- ***repoName*** - account and repo name to report violations to
- ***authToken*** - a Github token for a user that has access to comment on issues to the specified repo
- ***authToken*** - a GitHub token for a user that has access to comment on issues to the specified repo
- ***issueNumber*** - the issue or PR number currently being built

## Example Output

Here is an example of the output posted to a GitHub pr on a project using gnag to enforce quality checks.
Below are examples of output posted to a GitHub PR on a project using Gnag to enforce quality checks.

<img src="https://cloud.githubusercontent.com/assets/826036/11042826/641378e2-86e7-11e5-90ff-555a7cafd78c.png" />
### Inline Comments

Violations associated with a specific line in your PR will be posted as comments on that line:

![](assets/comments-inline.png)

### Aggregated Comments

Violations that cannot be associated with a specific line in your PR will be aggregated and posted in a single top-level PR comment. This will include:

- violations associated with entire files or projects;
- violations detected by individual reporters with invalid file location information.

![](assets/comments-aggregated.png)

## Example [Travis CI](http://travis-ci.org) Usage

Travis is a continuous integration service and is free for open source projects. Below is an example of
how to configure Gnag to run on Travis.

You must set an environment variable on your Travis instance for the `PR_BOT_AUTH_TOKEN` used to post comments back to Github.
You must set an environment variable on your Travis instance for the `PR_BOT_AUTH_TOKEN` used to post comments back to GitHub.

***.travis.yml***
```yml
Expand Down
Binary file added assets/comments-aggregated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/comments-inline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 6 additions & 7 deletions plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,21 @@ repositories {
}

dependencies {

testCompile group: 'junit', name: 'junit', version: '4.11'

//Gradle specific classes for plugin creation
compile gradleApi()

compile 'com.squareup.retrofit2:retrofit:2.0.2'

compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
compile 'com.github.stkent:githubdiffparser:1.0.0'
compile 'org.jetbrains:annotations:15.0'

//Built in checks
// Built in checks
compile 'com.puppycrawl.tools:checkstyle:6.16.1'
compile 'com.google.code.findbugs:findbugs:3.0.1'
compile 'net.sourceforge.pmd:pmd-java:5.4.1'

compile 'org.jetbrains:annotations:15.0'
// Testing
testCompile group: 'junit', name: 'junit', version: '4.11'
}

task wrapper(type: Wrapper) {
Expand Down
14 changes: 7 additions & 7 deletions plugin/src/main/groovy/com/btkelly/gnag/api/AuthInterceptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/
package com.btkelly.gnag.api;

import com.btkelly.gnag.extensions.GitHubExtension;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;

Expand All @@ -27,18 +27,18 @@
*/
public class AuthInterceptor implements Interceptor {

private final GitHubExtension gitHubExtension;
@NotNull
private final String authToken;

public AuthInterceptor(GitHubExtension gitHubExtension) {
this.gitHubExtension = gitHubExtension;
public AuthInterceptor(@NotNull final String authToken) {
this.authToken = authToken;
}

@Override
public Response intercept(Chain chain) throws IOException {

Request request = chain.request()
final Request request = chain.request()
.newBuilder()
.addHeader("Authorization", "token " + gitHubExtension.getAuthToken())
.addHeader("Authorization", "token " + authToken)
.build();

return chain.proceed(request);
Expand Down
89 changes: 64 additions & 25 deletions plugin/src/main/groovy/com/btkelly/gnag/api/GitHubApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,26 @@

import com.btkelly.gnag.extensions.GitHubExtension;
import com.btkelly.gnag.models.*;
import com.btkelly.gnag.utils.diffparser.DiffParserConverterFactory;
import com.btkelly.gnag.utils.gson.GsonConverterFactory;
import com.github.stkent.githubdiffparser.models.Diff;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
* Created by bobbake4 on 12/1/15.
*/
public class GitHubApi {

public enum Status {
OK,
FAIL
}


private final GitHubApiClient gitHubApiClient;
private final GitHubExtension gitHubExtension;

Expand All @@ -44,7 +46,8 @@ public GitHubApi(final GitHubExtension gitHubExtension) {
HttpLoggingInterceptor.Logger logger = System.out::println;

OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new AuthInterceptor(gitHubExtension))
.addInterceptor(new UserAgentInterceptor())
.addInterceptor(new AuthInterceptor(gitHubExtension.getAuthToken()))
.addInterceptor(new HttpLoggingInterceptor(logger).setLevel(HttpLoggingInterceptor.Level.NONE))
.build();

Expand All @@ -53,41 +56,77 @@ public GitHubApi(final GitHubExtension gitHubExtension) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(DiffParserConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();

gitHubApiClient = retrofit.create(GitHubApiClient.class);
}

public Status postGitHubComment(String comment) {
public void postGitHubPRCommentAsync(final String comment) {
gitHubApiClient.postPRComment(new GitHubPRComment(comment), gitHubExtension.getIssueNumber())
.enqueue(new DefaultCallback<>());
}

public void postUpdatedGitHubStatusAsync(final GitHubStatusType gitHubStatusType, final String prSha) {
gitHubApiClient.postUpdatedStatus(new GitHubStatus(gitHubStatusType), prSha)
.enqueue(new DefaultCallback<>());
}

@Nullable
public GitHubPRDetails getPRDetailsSync() {
try {
Response<GitHubComment> gitHubCommentResponse = gitHubApiClient.postComment(new GitHubComment(comment), gitHubExtension.getIssueNumber()).execute();
return gitHubCommentResponse.isSuccessful() ? Status.OK : Status.FAIL;
} catch (IOException ignored) {
return Status.FAIL;
Response<GitHubPRDetails> gitHubPRDetailsResponse = gitHubApiClient.getPRDetails(gitHubExtension.getIssueNumber()).execute();
return gitHubPRDetailsResponse.body();
} catch (final Exception e) {
e.printStackTrace();
return null;
}
}

public Status postUpdatedGitHubStatus(GitHubStatusType gitHubStatusType, String sha) {

@NotNull
public List<Diff> getPRDiffsSync() {
try {
Response<GitHubStatus> gitHubStatusResponse = gitHubApiClient.postUpdatedStatus(new GitHubStatus(gitHubStatusType), sha).execute();
return gitHubStatusResponse.isSuccessful() ? Status.OK : Status.FAIL;
} catch (IOException ignored) {
return Status.FAIL;
final Response<List<Diff>> gitHubPRDiffsResponse
= gitHubApiClient.getPRDiffs(gitHubExtension.getIssueNumber()).execute();

return gitHubPRDiffsResponse.body();
} catch (final Exception e) {
e.printStackTrace();
return new ArrayList<>();
}
}

public GitHubPullRequest getPullRequestDetails() {
public void postGitHubInlineCommentSync(
@NotNull final String body,
@NotNull final String prSha,
@NotNull final PRLocation prLocation) {

try {
Response<GitHubPullRequest> gitHubPullRequestResponse = gitHubApiClient.getPullRequest(gitHubExtension.getIssueNumber()).execute();
return gitHubPullRequestResponse.body();
} catch (Exception ignored) {
ignored.printStackTrace();
return null;
gitHubApiClient.postInlineComment(
new GitHubInlineComment(body, prSha, prLocation), gitHubExtension.getIssueNumber())
.execute();
} catch (final Exception e) {
e.printStackTrace();
}
}

private static final class DefaultCallback<T> implements Callback<T> {

private DefaultCallback() {
// This constructor intentionally left blank.
}

@Override
public void onResponse(final Call<T> call, final Response<T> response) {
// This method intentionally left blank.
}

@Override
public void onFailure(final Call<T> call, final Throwable t) {
t.printStackTrace();
}

}

}
34 changes: 26 additions & 8 deletions plugin/src/main/groovy/com/btkelly/gnag/api/GitHubApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,45 @@
*/
package com.btkelly.gnag.api;

import com.btkelly.gnag.models.GitHubComment;
import com.btkelly.gnag.models.GitHubPullRequest;
import com.btkelly.gnag.models.GitHubInlineComment;
import com.btkelly.gnag.models.GitHubPRComment;
import com.btkelly.gnag.models.GitHubPRDetails;
import com.btkelly.gnag.models.GitHubStatus;
import com.github.stkent.githubdiffparser.models.Diff;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.*;

import java.util.List;

/**
* Created by bobbake4 on 12/1/15.
*/
public interface GitHubApiClient {

// https://developer.github.com/v3/issues/comments/#create-a-comment
@POST("issues/{issueNumber}/comments")
Call<GitHubComment> postComment(@Body GitHubComment gitHubComment, @Path("issueNumber") String issueNumber);
@Headers("Accept: application/vnd.github.v3+json")
Call<GitHubPRComment> postPRComment(@Body GitHubPRComment gitHubPRComment, @Path("issueNumber") String issueNumber);

// https://developer.github.com/v3/repos/statuses/#create-a-status
@POST("statuses/{sha}")
@Headers("Accept: application/vnd.github.v3+json")
Call<GitHubStatus> postUpdatedStatus(@Body GitHubStatus gitHubStatus, @Path("sha") String sha);

// https://developer.github.com/v3/pulls/#get-a-single-pull-request
@GET("pulls/{issueNumber}")
@Headers("Accept: application/vnd.github.v3+json")
Call<GitHubPRDetails> getPRDetails(@Path("issueNumber") String issueNumber);

// https://developer.github.com/v3/pulls/#get-a-single-pull-request
// https://developer.github.com/v3/media/#commits-commit-comparison-and-pull-requests
@GET("pulls/{issueNumber}")
Call<GitHubPullRequest> getPullRequest(@Path("issueNumber") String issueNumber);
@Headers("Accept: application/vnd.github.v3.diff")
Call<List<Diff>> getPRDiffs(@Path("issueNumber") String issueNumber);

// https://developer.github.com/v3/pulls/comments/#create-a-comment
@POST("pulls/{issueNumber}/comments")
@Headers("Accept: application/vnd.github.v3+json")
Call<Void> postInlineComment(@Body GitHubInlineComment gitHubInlineComment, @Path("issueNumber") String issueNumber);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright 2016 Bryan Kelly
*
* 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
* http://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.
*/
package com.btkelly.gnag.api;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;

/**
* User-Agent header is required by GitHub API v3, see
* https://developer.github.com/v3/#user-agent-required
*/
public class UserAgentInterceptor implements Interceptor {

@Override
public Response intercept(final Chain chain) throws IOException {
final Request request = chain.request()
.newBuilder()
.addHeader("User-Agent", "btkelly-gnag")
.build();

return chain.proceed(request);
}

}
Loading

0 comments on commit 9b07f03

Please sign in to comment.