Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to cancel a Check #180

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -847,4 +847,51 @@ void issueInSummary(TestInfo testInfo) throws IOException {
assertEquals(CheckStatus.SUCCESS, check.status());
}
}

@Test
void cancelCheck(TestInfo testInfo) throws IOException {
try (var credentials = new HostCredentials(testInfo);
var tempFolder = new TemporaryDirectory()) {
var author = credentials.getHostedRepository();

// Populate the projects repository
var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType());
var masterHash = localRepo.resolve("master").orElseThrow();
localRepo.push(masterHash, author.getUrl(), "master", true);

// Make a change with a corresponding PR
var editHash = CheckableRepository.appendAndCommit(localRepo);
localRepo.push(editHash, author.getUrl(), "refs/heads/edit", true);
var pr = credentials.createPullRequest(author, "master", "edit", "This is a pull request");

// Verify no checks exists
var checks = pr.getChecks(editHash);
assertEquals(0, checks.size());

// Create a check that is running
var original = CheckBuilder.create("jcheck", editHash)
.title("jcheck title")
.summary("jcheck summary")
.build();
pr.createCheck(original);

// Verify check is created
checks = pr.getChecks(editHash);
assertEquals(1, checks.size());
var retrieved = checks.get("jcheck");
assertEquals("jcheck title", retrieved.title().get());
assertEquals("jcheck summary", retrieved.summary().get());
assertEquals(CheckStatus.IN_PROGRESS, retrieved.status());

// Cancel the check
var cancelled = CheckBuilder.from(retrieved).cancel().build();
pr.updateCheck(cancelled);
checks = pr.getChecks(editHash);
assertEquals(1, checks.size());
retrieved = checks.get("jcheck");
assertEquals("jcheck title", retrieved.title().get());
assertEquals("jcheck summary", retrieved.summary().get());
assertEquals(CheckStatus.CANCELLED, retrieved.status());
}
}
}
@@ -97,6 +97,18 @@ public CheckBuilder complete(boolean success, ZonedDateTime completedAt) {
return this;
}

public CheckBuilder cancel() {
status = CheckStatus.CANCELLED;
completedAt = ZonedDateTime.now();
return this;
}

public CheckBuilder cancel(ZonedDateTime completedAt) {
status = CheckStatus.CANCELLED;
this.completedAt = completedAt;
return this;
}

public CheckBuilder startedAt(ZonedDateTime startedAt) {
this.startedAt = startedAt;
return this;
@@ -25,5 +25,6 @@
public enum CheckStatus {
IN_PROGRESS,
SUCCESS,
FAILURE
FAILURE,
CANCELLED
}
@@ -292,8 +292,21 @@ public ZonedDateTime getUpdated() {

var completed = c.get("status").asString().equals("completed");
if (completed) {
checkBuilder.complete(c.get("conclusion").asString().equals("success"),
ZonedDateTime.parse(c.get("completed_at").asString()));
var conclusion = c.get("conclusion").asString();
var completedAt = ZonedDateTime.parse(c.get("completed_at").asString());
switch (conclusion) {
case "cancelled":
checkBuilder.cancel(completedAt);
break;
case "success":
checkBuilder.complete(true, completedAt);
break;
case "failure":
checkBuilder.complete(false, completedAt);
break;
default:
throw new IllegalStateException("Unexpected conclusion: " + conclusion);
}
}
if (c.contains("external_id")) {
checkBuilder.metadata(c.get("external_id").asString());
@@ -314,22 +327,21 @@ public ZonedDateTime getUpdated() {

@Override
public void createCheck(Check check) {
var checkQuery = JSON.object();
checkQuery.put("name", check.name());
checkQuery.put("head_branch", json.get("head").get("ref").asString());
checkQuery.put("head_sha", check.hash().hex());
checkQuery.put("started_at", check.startedAt().format(DateTimeFormatter.ISO_INSTANT));
checkQuery.put("status", "in_progress");
check.metadata().ifPresent(metadata -> checkQuery.put("external_id", metadata));

request.post("check-runs").body(checkQuery).execute();
// update and create are currenly identical operations, both do an HTTP
// POST to the /repos/:owner/:repo/check-runs endpoint. There is an additional
// endpoint explicitly for updating check-runs, but that is not currently used.
updateCheck(check);
}

@Override
public void updateCheck(Check check) {
JSONObject outputQuery = null;
var completedQuery = JSON.object();
completedQuery.put("name", check.name());
completedQuery.put("head_branch", json.get("head").get("ref"));
completedQuery.put("head_sha", check.hash().hex());

if (check.title().isPresent() && check.summary().isPresent()) {
outputQuery = JSON.object();
var outputQuery = JSON.object();
outputQuery.put("title", check.title().get());
outputQuery.put("summary", check.summary().get());

@@ -359,25 +371,20 @@ public void updateCheck(Check check) {
}

outputQuery.put("annotations", annotations);
completedQuery.put("output", outputQuery);
}

var completedQuery = JSON.object();
completedQuery.put("name", check.name());
completedQuery.put("head_branch", json.get("head").get("ref"));
completedQuery.put("head_sha", check.hash().hex());
completedQuery.put("status", "completed");
completedQuery.put("started_at", check.startedAt().format(DateTimeFormatter.ISO_INSTANT));
check.metadata().ifPresent(metadata -> completedQuery.put("external_id", metadata));

if (check.status() != CheckStatus.IN_PROGRESS) {
completedQuery.put("conclusion", check.status() == CheckStatus.SUCCESS ? "success" : "failure");
if (check.status() == CheckStatus.IN_PROGRESS) {
completedQuery.put("status", "in_progress");
} else {
completedQuery.put("status", "completed");
completedQuery.put("conclusion", check.status().name().toLowerCase());
completedQuery.put("completed_at", check.completedAt().orElse(ZonedDateTime.now(ZoneOffset.UTC))
.format(DateTimeFormatter.ISO_INSTANT));
}

if (outputQuery != null) {
completedQuery.put("output", outputQuery);
}
completedQuery.put("started_at", check.startedAt().format(DateTimeFormatter.ISO_INSTANT));
check.metadata().ifPresent(metadata -> completedQuery.put("external_id", metadata));

request.post("check-runs").body(completedQuery).execute();
}
@@ -364,41 +364,59 @@ private String encodeMarkdown(String message) {
entry -> {
var checkBuilder = CheckBuilder.create(entry.getValue().group(1), hash);
checkBuilder.startedAt(entry.getKey().createdAt());
if (!entry.getValue().group(2).equals("RUNNING")) {
checkBuilder.complete(entry.getValue().group(2).equals("SUCCESS"), entry.getKey().updatedAt());
var status = entry.getValue().group(2);
var completedAt = entry.getKey().updatedAt();
switch (status) {
case "RUNNING":
// do nothing
break;
case "SUCCESS":
checkBuilder.complete(true, completedAt);
break;
case "FAILURE":
checkBuilder.complete(false, completedAt);
break;
case "CANCELLED":
checkBuilder.cancel(completedAt);
break;
default:
throw new IllegalStateException("Unknown status: " + status);
}
if (!entry.getValue().group(3).equals("NONE")) {
checkBuilder.metadata(new String(Base64.getDecoder().decode(entry.getValue().group(3)), StandardCharsets.UTF_8));
}
var checkBodyMatcher = checkBodyPattern.matcher(entry.getKey().body());
if (checkBodyMatcher.find()) {
checkBuilder.title(checkBodyMatcher.group(1));
// escapeMarkdown adds an additional space before the newline
var title = checkBodyMatcher.group(1);
var nonEscapedTitle = title.substring(0, title.length() - 2);
checkBuilder.title(nonEscapedTitle);
checkBuilder.summary(checkBodyMatcher.group(2));
}
return checkBuilder.build();
}));
}

@Override
public void createCheck(Check check) {
log.info("Looking for previous status check comment");
private String statusFor(Check check) {
switch (check.status()) {
case IN_PROGRESS:
return "RUNNING";
case SUCCESS:
return "SUCCESS";
case FAILURE:
return "FAILURE";
case CANCELLED:
return "CANCELLED";
default:
throw new RuntimeException("Unknown check status");
}
}

var previous = getStatusCheckComment(check.name());
var body = ":hourglass_flowing_sand: The merge request check **" + check.name() + "** is currently running...";
var metadata = "NONE";
private String metadataFor(Check check) {
if (check.metadata().isPresent()) {
metadata = Base64.getEncoder().encodeToString(check.metadata().get().getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(check.metadata().get().getBytes(StandardCharsets.UTF_8));
}
var message = String.format(checkMarker, check.name()) + "\n" +
String.format(checkResultMarker,
check.name(),
"RUNNING",
check.hash(),
metadata
) + "\n" + encodeMarkdown(body);

previous.ifPresentOrElse(p -> updateComment(p.id(), message),
() -> addComment(message));
return "NONE";
}

private String linkToDiff(String path, Hash hash, int line) {
@@ -408,71 +426,87 @@ private String linkToDiff(String path, Hash hash, int line) {
.build() + "#L" + Integer.toString(line) + ")";
}

@Override
public void updateCheck(Check check) {
log.info("Looking for previous status check comment");

var previous = getStatusCheckComment(check.name())
.orElseGet(() -> addComment("Progress deleted?"));

String status;
switch (check.status()) {
private String bodyFor(Check check) {
var status = check.status();
String body;
switch (status) {
case IN_PROGRESS:
status = "RUNNING";
body = ":hourglass_flowing_sand: The merge request check **" + check.name() + "** is currently running...";
break;
case SUCCESS:
status = "SUCCESS";
body = ":tada: The merge request check **" + check.name() + "** completed successfully!";
break;
case FAILURE:
status = "FAILURE";
body = ":warning: The merge request check **" + check.name() + "** identified the following issues:";
break;
case CANCELLED:
body = ":x: The merge request check **" + check.name() + "** has been cancelled.";
break;
default:
throw new RuntimeException("Unknown check status");
}

var metadata = "NONE";
if (check.metadata().isPresent()) {
metadata = Base64.getEncoder().encodeToString(check.metadata().get().getBytes(StandardCharsets.UTF_8));
}
var markers = String.format(checkMarker, check.name()) + "\n" + String.format(checkResultMarker, check.name(),
status, check.hash(), metadata);

String body;
if (check.status() == CheckStatus.SUCCESS) {
body = ":tada: The merge request check **" + check.name() + "** completed successfully!";
} else {
if (check.status() == CheckStatus.IN_PROGRESS) {
body = ":hourglass_flowing_sand: The merge request check **" + check.name() + "** is currently running...";
} else {
body = ":warning: The merge request check **" + check.name() + "** identified the following issues:";
}
if (check.title().isPresent() && check.summary().isPresent()) {
body += encodeMarkdown("\n" + "##### " + check.title().get() + "\n" + check.summary().get());

for (var annotation : check.annotations()) {
var annotationString = " - ";
switch (annotation.level()) {
case NOTICE:
annotationString += "Notice: ";
break;
case WARNING:
annotationString += "Warning: ";
break;
case FAILURE:
annotationString += "Failure: ";
break;
}
annotationString += linkToDiff(annotation.path(), check.hash(), annotation.startLine());
annotationString += "\n - " + annotation.message().lines().collect(Collectors.joining("\n - "));

body += "\n" + annotationString;
if ( check.title().isPresent() && check.summary().isPresent()) {
body += encodeMarkdown("\n" + "##### " + check.title().get() + "\n" + check.summary().get());

for (var annotation : check.annotations()) {
var annotationString = " - ";
switch (annotation.level()) {
case NOTICE:
annotationString += "Notice: ";
break;
case WARNING:
annotationString += "Warning: ";
break;
case FAILURE:
annotationString += "Failure: ";
break;
}
annotationString += linkToDiff(annotation.path(), check.hash(), annotation.startLine());
annotationString += "\n - " + annotation.message().lines().collect(Collectors.joining("\n - "));

body += "\n" + annotationString;
}
}

updateComment(previous.id(), markers + "\n" + body);
return body;
}

private void updateCheckComment(Optional<Comment> previous, Check check) {
var status = statusFor(check);
var metadata = metadataFor(check);
var markers = String.format(checkMarker, check.name()) + "\n" +
String.format(checkResultMarker,
check.name(),
status,
check.hash(),
metadata);

var body = bodyFor(check);
var message = markers + "\n" + body;
previous.ifPresentOrElse(
p -> updateComment(p.id(), message),
() -> addComment(message));
}

@Override
public void createCheck(Check check) {
log.info("Looking for previous status check comment");

var previous = getStatusCheckComment(check.name());
updateCheckComment(previous, check);
}

@Override
public void updateCheck(Check check) {
log.info("Looking for previous status check comment");

var previous = getStatusCheckComment(check.name())
.orElseGet(() -> addComment("Progress deleted?"));
updateCheckComment(Optional.of(previous), check);
}


@Override
public void setState(State state) {
request.put("")
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.