Skip to content

Commit

Permalink
Add support for updating a previous message. (#679)
Browse files Browse the repository at this point in the history
Co-authored-by: Eric Brody <[email protected]>
  • Loading branch information
underscorebrody and Eric Brody authored Mar 20, 2020
1 parent 9fe6fa5 commit 08a79ef
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 11 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@ slackSend(
)
```

#### Update Messages

You can update the content of a previously sent message using the pipeline step.
The step returns an object which you can use to retrieve the timestamp and channelId
NOTE: The slack API requires the channel ID for `chat.update` calls.

Example:

```groovy
def slackResponse = slackSend(channel: "updating-stuff", message: "Here is the primary message")
slackSend(channel: slackResponse.channelId, message: "Update message now", timestamp: slackResponse.ts)
```

This feature requires [botUser](#bot-user-mode) mode.

#### Unfurling Links

You can allow link unfurling if you send the message as text. This only works in a text message, as attachments cannot be unfurled.
Expand Down
21 changes: 17 additions & 4 deletions src/main/java/jenkins/plugins/slack/SlackRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
public class SlackRequest {
private String message;
private String color;
private String timestamp;
private JSONArray attachments;
private JSONArray blocks;

private SlackRequest(String message, String color, JSONArray attachments, JSONArray blocks) {
private SlackRequest(String message, String color, JSONArray attachments, JSONArray blocks, String timestamp) {
if (blocks != null && color != null) {
throw new IllegalArgumentException("Color is not supported when blocks are set");
}
Expand All @@ -18,6 +19,7 @@ private SlackRequest(String message, String color, JSONArray attachments, JSONAr
this.color = color;
this.attachments = attachments;
this.blocks = blocks;
this.timestamp = timestamp;
}

public static SlackRequestBuilder builder() {
Expand All @@ -28,6 +30,10 @@ public String getMessage() {
return message;
}

public String getTimestamp() {
return timestamp;
}

public String getColor() {
return color;
}
Expand All @@ -47,23 +53,25 @@ public boolean equals(Object o) {
SlackRequest that = (SlackRequest) o;
return Objects.equals(message, that.message) &&
Objects.equals(color, that.color) &&
Objects.equals(timestamp, that.timestamp) &&
Objects.equals(attachments, that.attachments) &&
Objects.equals(blocks, that.blocks);
}

@Override
public String toString() {
return String.format("SlackRequest{message='%s', color='%s', attachments=%s, blocks=%s}", message, color, attachments, blocks);
return String.format("SlackRequest{message='%s', color='%s', attachments=%s, blocks=%s, timestamp='%s'}", message, color, attachments, blocks, timestamp);
}

@Override
public int hashCode() {
return Objects.hash(message, color, attachments, blocks);
return Objects.hash(message, color, attachments, blocks, timestamp);
}

public static class SlackRequestBuilder {
private String message;
private String color;
private String timestamp;
private JSONArray attachments;
private JSONArray blocks;

Expand All @@ -80,6 +88,11 @@ public SlackRequestBuilder withColor(String color) {
return this;
}

public SlackRequestBuilder withTimestamp(String timestamp) {
this.timestamp = timestamp;
return this;
}

public SlackRequestBuilder withAttachments(JSONArray attachments) {
this.attachments = attachments;
return this;
Expand All @@ -91,7 +104,7 @@ public SlackRequestBuilder withBlocks(JSONArray blocks) {
}

public SlackRequest build() {
return new SlackRequest(message, color, attachments, blocks);
return new SlackRequest(message, color, attachments, blocks, timestamp);
}

}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/jenkins/plugins/slack/SlackService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ public interface SlackService {

boolean publish(String message, String color);

boolean publish(String message, String color, String timestamp);

boolean publish(String message, JSONArray attachments, String color);

boolean publish(String message, JSONArray attachments, String color, String timestamp);

String getResponseString();
}
49 changes: 48 additions & 1 deletion src/main/java/jenkins/plugins/slack/StandardSlackService.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class StandardSlackService implements SlackService {
private String populatedToken;
private boolean notifyCommitters;
private SlackUserIdResolver userIdResolver;
private String timestamp;


/**
Expand Down Expand Up @@ -111,6 +112,7 @@ public StandardSlackService(StandardSlackServiceBuilder standardSlackServiceBuil
this.populatedToken = standardSlackServiceBuilder.populatedToken;
this.notifyCommitters = standardSlackServiceBuilder.notifyCommitters;
this.userIdResolver = standardSlackServiceBuilder.userIdResolver;
this.timestamp = standardSlackServiceBuilder.timestamp;
}

public static StandardSlackServiceBuilder builder() {
Expand Down Expand Up @@ -145,6 +147,7 @@ public boolean publish(SlackRequest slackRequest) {
boolean result = true;

String message = slackRequest.getMessage();
String timestamp = slackRequest.getTimestamp();
JSONArray attachments = slackRequest.getAttachments();
JSONArray blocks = slackRequest.getBlocks();
String color = slackRequest.getColor();
Expand Down Expand Up @@ -190,6 +193,13 @@ public boolean publish(SlackRequest slackRequest) {
json.put("unfurl_links", "true");
json.put("unfurl_media", "true");

String apiEndpoint = "chat.postMessage";

if (StringUtils.isNotEmpty(timestamp)) {
json.put("ts", timestamp);
apiEndpoint = "chat.update";
}

if (baseUrl != null) {
correctMisconfigurationOfBaseUrl();
}
Expand All @@ -203,7 +213,7 @@ public boolean publish(SlackRequest slackRequest) {
post = new HttpPost(url);

} else {
url = "https://slack.com/api/chat.postMessage";
url = "https://slack.com/api/" + apiEndpoint;

post = new HttpPost(url);
post.setHeader("Authorization", "Bearer " + populatedToken);
Expand Down Expand Up @@ -300,6 +310,43 @@ public boolean publish(String message, JSONArray attachments, String color) {
);
}

@Override
public boolean publish(String message, String color, String timestamp) {
//prepare attachments first
JSONObject field = new JSONObject();
field.put("short", false);
field.put("value", message);

JSONArray fields = new JSONArray();
fields.add(field);

JSONObject attachment = new JSONObject();
attachment.put("fallback", message);
attachment.put("color", color);
attachment.put("fields", fields);
JSONArray mrkdwn = new JSONArray();
mrkdwn.add("pretext");
mrkdwn.add("text");
mrkdwn.add("fields");
attachment.put("mrkdwn_in", mrkdwn);
JSONArray attachments = new JSONArray();
attachments.add(attachment);

return publish(null, attachments, color, timestamp);
}

@Override
public boolean publish(String message, JSONArray attachments, String color, String timestamp) {
return publish(
SlackRequest.builder()
.withMessage(message)
.withTimestamp(timestamp)
.withAttachments(attachments)
.withColor(color)
.build()
);
}


private String getTokenToUse(String authTokenCredentialId, String token) {
if (!StringUtils.isEmpty(authTokenCredentialId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class StandardSlackServiceBuilder {
String populatedToken;
boolean notifyCommitters;
SlackUserIdResolver userIdResolver;
String timestamp;

public StandardSlackServiceBuilder() {
}
Expand Down Expand Up @@ -75,6 +76,11 @@ public StandardSlackServiceBuilder withSlackUserIdResolver(SlackUserIdResolver u
return this;
}

public StandardSlackServiceBuilder withTimestamp(String timestamp) {
this.timestamp = timestamp;
return this;
}

public StandardSlackService build() { return new StandardSlackService(this); }

}
26 changes: 23 additions & 3 deletions src/main/java/jenkins/plugins/slack/workflow/SlackSendStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class SlackSendStep extends Step {
private static final Logger logger = Logger.getLogger(SlackSendStep.class.getName());

private String message;
private String timestamp;
private String color;
private String token;
private String tokenCredentialId;
Expand All @@ -68,6 +69,10 @@ public String getMessage() {
return message;
}

public String getTimestamp() {
return timestamp;
}

public String getColor() {
return color;
}
Expand Down Expand Up @@ -166,6 +171,11 @@ public void setMessage(String message) {
this.message = Util.fixEmpty(message);
}

@DataBoundSetter
public void setTimestamp(String timestamp) {
this.timestamp = Util.fixEmpty(timestamp);
}

public boolean getReplyBroadcast() {
return replyBroadcast;
}
Expand Down Expand Up @@ -283,7 +293,7 @@ protected SlackResponse run() throws Exception {

listener.getLogger().println(Messages.slackSendStepValues(
defaultIfEmpty(baseUrl), defaultIfEmpty(teamDomain), channel, defaultIfEmpty(color), botUser,
defaultIfEmpty(tokenCredentialId), notifyCommitters, defaultIfEmpty(iconEmoji), defaultIfEmpty(username))
defaultIfEmpty(tokenCredentialId), notifyCommitters, defaultIfEmpty(iconEmoji), defaultIfEmpty(username), defaultIfEmpty(step.timestamp))
);
final String populatedToken;
try {
Expand Down Expand Up @@ -311,7 +321,11 @@ protected SlackResponse run() throws Exception {

final boolean publishSuccess;
if (sendAsText) {
publishSuccess = slackService.publish(step.message, new JSONArray(), color);
if (step.timestamp != null) {
publishSuccess = slackService.publish(step.message, new JSONArray(), color, step.timestamp);
} else {
publishSuccess = slackService.publish(step.message, new JSONArray(), color);
}
} else if (step.attachments != null) {
JSONArray jsonArray = getAttachmentsAsJSONArray();
for (Object object : jsonArray) {
Expand All @@ -327,6 +341,7 @@ protected SlackResponse run() throws Exception {
.withMessage(step.message)
.withAttachments(jsonArray)
.withColor(color)
.withTimestamp(step.timestamp)
.build()
);
} else if (step.blocks != null) {
Expand All @@ -336,10 +351,15 @@ protected SlackResponse run() throws Exception {
SlackRequest.builder()
.withMessage(step.message)
.withBlocks(jsonArray)
.withTimestamp(step.timestamp)
.build()
);
} else if (step.message != null) {
publishSuccess = slackService.publish(step.message, color);
if (step.timestamp != null) {
publishSuccess = slackService.publish(step.message, color, step.timestamp);
} else {
publishSuccess = slackService.publish(step.message, color);
}
} else {
listener.error(Messages
.notificationFailedWithException(new IllegalArgumentException("No message, attachments or blocks provided")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ slackUserIdsFromCommittersDisplayName=Resolve Slack UserIds from Changeset Autho
# Messages to display in the build logs
notificationFailed=Slack notification failed. See Jenkins logs for details.
notificationFailedWithException=Slack notification failed with exception: {0}
slackSendStepValues=Slack Send Pipeline step running, values are - baseUrl: {0}, teamDomain: {1}, channel: {2}, color: {3}, botUser: {4}, tokenCredentialId: {5}, notifyCommitters: {6}, iconEmoji: {7}, username: {8}
slackSendStepValues=Slack Send Pipeline step running, values are - baseUrl: {0}, teamDomain: {1}, channel: {2}, color: {3}, botUser: {4}, tokenCredentialId: {5}, notifyCommitters: {6}, iconEmoji: {7}, username: {8}, timestamp: {9}
slackSendStepValuesEmptyMessage=<empty>
failedToParseSlackResponse=Could not parse response from slack, potentially because of invalid configuration (botUser: true and baseUrl set), response: {0}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Allows updating an existing message instead of posting a new one.
</div>
10 changes: 10 additions & 0 deletions src/test/java/jenkins/plugins/slack/SlackNotifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,21 @@ public boolean publish(String message, String color) {
return response;
}

@Override
public boolean publish(String message, String color, String timestamp) {
return response;
}

@Override
public boolean publish(String message, JSONArray attachments, String color) {
return response;
}

@Override
public boolean publish(String message, JSONArray attachments, String color, String timestamp) {
return response;
}

public void setResponse(boolean response) {
this.response = response;
}
Expand Down
16 changes: 16 additions & 0 deletions src/test/java/jenkins/plugins/slack/StandardSlackServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,22 @@ public void sendAsBotUserInThreadReturnsTrue() {
assertTrue(service.publish("message"));
}

@Test
public void sendAsBotUserWithUpdate() {
StandardSlackServiceStub service = new StandardSlackServiceStub(
StandardSlackService.builder()
.withBaseUrl("")
.withTeamDomain("domain")
.withBotUser(true)
.withRoomId("#room1:1528317530")
.withPopulatedToken("token")
.withTimestamp("132124"));
CloseableHttpClientStub httpClientStub = new CloseableHttpClientStub();
httpClientStub.setHttpStatus(HttpStatus.SC_OK);
service.setHttpClient(httpClientStub);
assertTrue(service.publish("message"));
}

@Test
public void populatedTokenIsUsed() {
final String populatedToken = "secret-text";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ public void configRoundTrip() throws Exception {
public void test_global_config_override() throws Exception {
WorkflowJob job = jenkinsRule.jenkins.createProject(WorkflowJob.class, "workflow");
//just define message
job.setDefinition(new CpsFlowDefinition("slackSend(message: 'message', baseUrl: 'baseUrl', teamDomain: 'teamDomain', token: 'token', tokenCredentialId: 'tokenCredentialId', channel: '#channel', color: 'good', iconEmoji: ':+1:', username: 'username');", true));
job.setDefinition(new CpsFlowDefinition("slackSend(message: 'message', baseUrl: 'baseUrl', teamDomain: 'teamDomain', token: 'token', tokenCredentialId: 'tokenCredentialId', channel: '#channel', color: 'good', iconEmoji: ':+1:', username: 'username', timestamp: '124124.12412');", true));
WorkflowRun run = jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0).get());
//everything should come from step configuration
jenkinsRule.assertLogContains(Messages.slackSendStepValues("baseUrl/", "teamDomain", "#channel", "good", false, "tokenCredentialId", false, ":+1:", "username"), run);
jenkinsRule.assertLogContains(Messages.slackSendStepValues("baseUrl/", "teamDomain", "#channel", "good", false, "tokenCredentialId", false, ":+1:", "username", "124124.12412"), run);
}

@Test
Expand Down
Loading

0 comments on commit 08a79ef

Please sign in to comment.