// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package com.uber.jenkins.phabricator.conduit;
import com.uber.jenkins.phabricator.lint.LintResults;
import com.uber.jenkins.phabricator.unit.UnitResults;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import java.io.IOException;
import java.util.Map;
/**
* DifferentialClient handles all interaction with conduit/arc for differentials
*/
public class DifferentialClient {
private final String diffID;
private final ConduitAPIClient conduit;
public DifferentialClient(String diffID, ConduitAPIClient conduit) {
this.diffID = diffID;
this.conduit = conduit;
}
/**
* Posts a comment to a differential
* @param revisionID the revision ID (e.g. "D1234" without the "D")
* @param message the content of the comment
* @param silent whether or not to trigger an email
* @param action phabricator comment action, e.g. 'resign', 'reject', 'none'
* @return the Conduit API response
* @throws IOException if there is a network error talking to Conduit
* @throws ConduitAPIException if any error is experienced talking to Conduit
*/
public JSONObject postComment(String revisionID, String message, boolean silent, String action) throws IOException, ConduitAPIException {
JSONObject params = new JSONObject();
params.element("revision_id", revisionID)
.element("action", action)
.element("message", message)
.element("silent", silent);
return this.callConduit("differential.createcomment", params);
}
/**
* Fetch a differential from Conduit
* @return the Conduit API response
* @throws IOException if there is a network error talking to Conduit
* @throws ConduitAPIException if any error is experienced talking to Conduit
*/
public JSONObject fetchDiff() throws IOException, ConduitAPIException {
JSONObject params = new JSONObject().element("ids", new String[]{diffID});
JSONObject query = this.callConduit("differential.querydiffs", params);
JSONObject response;
try {
response = query.getJSONObject("result");
} catch (JSONException e) {
throw new ConduitAPIException(
String.format("No 'result' object found in conduit call: (%s) %s",
e.getMessage(),
query.toString(2)));
}
try {
return response.getJSONObject(diffID);
} catch (JSONException e) {
throw new ConduitAPIException(
String.format("Unable to find '%s' key in 'result': (%s) %s",
diffID,
e.getMessage(),
response.toString(2)));
}
}
/**
* Sets a sendHarbormasterMessage build status
* @param phid Phabricator object ID
* @param pass whether or not the build passed
* @param unitResults the results from the unit tests
* @param coverage the results from the coverage provider
* @return the Conduit API response
* @throws IOException if there is a network error talking to Conduit
* @throws ConduitAPIException if any error is experienced talking to Conduit
*/
public JSONObject sendHarbormasterMessage(String phid, boolean pass, UnitResults unitResults,
Map<String, String> coverage,
LintResults lintResults) throws ConduitAPIException, IOException {
return new HarbormasterClient(conduit).sendHarbormasterMessage(phid, pass, unitResults, coverage, lintResults);
}
/**
* Uploads a uri as an 'artifact' for Harbormaster to display
* @param phid Phabricator object ID
* @param buildUri Uri to display, presumably the jenkins builds
* @return the Conduit API response
* @throws IOException if there is a network error talking to Conduit
* @throws ConduitAPIException if any error is experienced talking to Conduit
*/
public JSONObject sendHarbormasterUri(String phid, String buildUri) throws ConduitAPIException, IOException {
return new HarbormasterClient(conduit).sendHarbormasterUri(phid, buildUri);
}
/**
* Post a comment on the differential
* @param revisionID the revision ID (e.g. "D1234" without the "D")
* @param message the string message to post
* @return the Conduit API response
* @throws IOException if there is a network error talking to Conduit
* @throws ConduitAPIException if any error is experienced talking to Conduit
*/
public JSONObject postComment(String revisionID, String message) throws ConduitAPIException, IOException {
return postComment(revisionID, message, true, "none");
}
/**
* Fetch the commit message for the revision. This isn't available on the diff, so it requires a separate query.
* @param revisionID The ID of the revision, e.g. for "D123" this would be "123"
* @return A \n-separated string of the commit message
* @throws ConduitAPIException
* @throws IOException
*/
public String getCommitMessage(String revisionID) throws ConduitAPIException, IOException {
JSONObject params = new JSONObject().element("revision_id", revisionID);
JSONObject query = callConduit("differential.getcommitmessage", params);
// NOTE: When you run this with `arc call-conduit dfferential.getcommitmessage` (from the command-line),
// it comes back as "response". But it's "result" when running through this conduit API.
return query.getString("result");
}
protected JSONObject callConduit(String methodName, JSONObject params) throws ConduitAPIException, IOException {
return conduit.perform(methodName, params);
}
}