-
Notifications
You must be signed in to change notification settings - Fork 82
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
Update slack upload flow v2 #292
Changes from 4 commits
c4e1eac
2ec5896
7689684
1008db3
2a19472
1344c23
8e52ad2
382d73d
c1848ce
99f9184
c54bc68
1807336
93058bd
a7ddf20
33d05eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,37 @@ | ||
package guru.qa.allure.notifications.clients.slack; | ||
|
||
import com.google.gson.Gson; | ||
import guru.qa.allure.notifications.clients.Notifier; | ||
import guru.qa.allure.notifications.clients.slack.model.ImageBlock; | ||
import guru.qa.allure.notifications.clients.slack.model.SectionBlock; | ||
import guru.qa.allure.notifications.clients.slack.model.TextObject; | ||
import guru.qa.allure.notifications.config.slack.Slack; | ||
import guru.qa.allure.notifications.exceptions.MessagingException; | ||
import guru.qa.allure.notifications.template.MessageTemplate; | ||
import guru.qa.allure.notifications.template.data.MessageData; | ||
import kong.unirest.ContentType; | ||
import kong.unirest.Unirest; | ||
import kong.unirest.json.JSONArray; | ||
import kong.unirest.json.JSONObject; | ||
import lombok.SneakyThrows; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.http.HttpHeaders; | ||
import org.apache.http.HttpStatus; | ||
import org.apache.http.client.methods.CloseableHttpResponse; | ||
import org.apache.http.client.methods.HttpUriRequest; | ||
import org.apache.http.client.methods.RequestBuilder; | ||
import org.apache.http.entity.StringEntity; | ||
import org.apache.http.entity.mime.HttpMultipartMode; | ||
import org.apache.http.entity.mime.MultipartEntityBuilder; | ||
import org.apache.http.impl.client.CloseableHttpClient; | ||
import org.apache.http.impl.client.HttpClients; | ||
import org.apache.http.util.EntityUtils; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.IOException; | ||
import java.io.UnsupportedEncodingException; | ||
import java.util.Arrays; | ||
|
||
@Slf4j | ||
public class SlackClient implements Notifier { | ||
private final Slack slack; | ||
|
||
|
@@ -18,28 +40,152 @@ public SlackClient(Slack slack) { | |
} | ||
|
||
@Override | ||
public void sendText(MessageData messageData) throws MessagingException { | ||
String body = String.format("channel=%s&text=%s", | ||
slack.getChat(), new MessageTemplate(messageData).createMessageFromTemplate(slack.getTemplatePath())); | ||
|
||
Unirest.post("https://slack.com/api/chat.postMessage") | ||
.header("Authorization", "Bearer " + slack.getToken()) | ||
.header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) | ||
.body(body) | ||
.asString() | ||
.getBody(); | ||
public void sendText(MessageData messageData) { | ||
try (CloseableHttpClient client = HttpClients.createDefault()) { | ||
postMessage(client, messageData); | ||
} catch (IOException e) { | ||
log.error("Failed to post message to Slack", e); | ||
} | ||
} | ||
|
||
@Override | ||
public void sendPhoto(MessageData messageData, byte[] chartImage) throws MessagingException { | ||
Unirest.post("https://slack.com/api/files.upload") | ||
.header("Authorization", "Bearer " + slack.getToken()) | ||
.field("file", new ByteArrayInputStream(chartImage), ContentType.IMAGE_PNG, "chart.png") | ||
.field("channels", slack.getChat()) | ||
.field("filename", " ") | ||
.field("initial_comment", new MessageTemplate(messageData).createMessageFromTemplate( | ||
slack.getTemplatePath())) | ||
.asString() | ||
.getBody(); | ||
@SneakyThrows | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is it needed? |
||
public void sendPhoto(MessageData messageData, byte[] chartImage) { | ||
try (CloseableHttpClient client = HttpClients.createDefault()) { | ||
String title = "chart.png"; | ||
JSONObject uploadResponse = getUploadURLExternal(client, title, chartImage); | ||
|
||
String fileId = uploadResponse.getString("file_id"); | ||
String uploadUrl = uploadResponse.getString("upload_url"); | ||
|
||
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create() | ||
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE) | ||
.addBinaryBody("file", new ByteArrayInputStream(chartImage), org.apache.http.entity.ContentType.DEFAULT_BINARY, "chart"); | ||
|
||
if (!uploadFile(uploadUrl, multipartEntityBuilder, client)) { | ||
log.error("Failed to upload file to Slack"); | ||
} | ||
|
||
//todo wait until file processing to complete | ||
Thread.sleep(2000); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there way to check is file processing completed instead of hardcoded wait? |
||
|
||
JSONObject completeResponse = completeUploadExternal(client, fileId, title); | ||
|
||
String filePermalink = completeResponse.getJSONArray("files").getJSONObject(0).getString("permalink"); | ||
|
||
postMessage(client, messageData, filePermalink); | ||
|
||
} catch (IOException e) { | ||
log.error("Failed to post message with file to Slack", e); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrap into |
||
} | ||
} | ||
|
||
private void postMessage(CloseableHttpClient client, MessageData messageData) throws UnsupportedEncodingException { | ||
postMessage(client, messageData, ""); | ||
} | ||
|
||
private void postMessage(CloseableHttpClient client, MessageData messageData, String fileUrl) throws UnsupportedEncodingException { | ||
HttpUriRequest request; | ||
if (fileUrl.isEmpty()) { | ||
request = RequestBuilder | ||
.post("https://slack.com/api/chat.postMessage") | ||
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) | ||
.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) | ||
.setEntity(new StringEntity(getTextData(messageData))) | ||
.build(); | ||
} else { | ||
request = RequestBuilder | ||
.post("https://slack.com/api/chat.postMessage") | ||
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) | ||
.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) | ||
.addParameter("channel", slack.getChat()) | ||
.addParameter("blocks", getBlocksForPostMessage(messageData, fileUrl)) | ||
.build(); | ||
} | ||
|
||
try (CloseableHttpResponse responseBody = client.execute(request)) { | ||
if (responseBody.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { | ||
log.info(EntityUtils.toString(responseBody.getEntity())); | ||
} | ||
} catch (IOException | UnsupportedOperationException e) { | ||
log.error("Error post message", e); | ||
} | ||
} | ||
|
||
private boolean uploadFile(String uploadUrl, MultipartEntityBuilder multipartEntityBuilder, CloseableHttpClient client) throws IOException { | ||
HttpUriRequest request = RequestBuilder | ||
.post(uploadUrl) | ||
.setEntity(multipartEntityBuilder.build()) | ||
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) | ||
.build(); | ||
|
||
try (CloseableHttpResponse responseBody = client.execute(request)) { | ||
if (responseBody.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
private String getTextData(MessageData messageData) { | ||
return String.format("channel=%s&text=%s", slack.getChat(), proceedMessageData(messageData)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
} | ||
|
||
private String getBlocksForPostMessage(MessageData messageData, String imageUrl) { | ||
SectionBlock sectionBlock = SectionBlock.builder().text(TextObject.builder().text(proceedMessageData(messageData)).build()).build(); | ||
ImageBlock imageBlock = ImageBlock.builder().imageUrl(imageUrl).altText("chart").build(); | ||
return new Gson().toJson(Arrays.asList(sectionBlock, imageBlock)); | ||
} | ||
|
||
private String proceedMessageData(MessageData messageData) { | ||
String text = ""; | ||
try { | ||
text = new MessageTemplate(messageData).createMessageFromTemplate(slack.getTemplatePath()); | ||
} catch (MessagingException e) { | ||
log.error("Could not create message from template", e); | ||
} | ||
return text; | ||
} | ||
|
||
private JSONObject completeUploadExternal(CloseableHttpClient client, String fileId, String title) { | ||
JSONObject completeUploadResponse = new JSONObject(); | ||
JSONArray array = new JSONArray(); | ||
JSONObject node = new JSONObject(); | ||
node.put("id", fileId); | ||
node.put("title", title); | ||
array.put(node); | ||
|
||
HttpUriRequest request = RequestBuilder | ||
.post("https://slack.com/api/files.completeUploadExternal") | ||
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) | ||
.addParameter("files", array.toString()) | ||
.build(); | ||
try (CloseableHttpResponse responseBody = client.execute(request)) { | ||
if (responseBody.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { | ||
completeUploadResponse = new JSONObject(EntityUtils.toString(responseBody.getEntity())); | ||
} | ||
} catch (IOException | UnsupportedOperationException e) { | ||
log.error("Error complete upload file", e); | ||
} | ||
return completeUploadResponse; | ||
} | ||
|
||
private JSONObject getUploadURLExternal(CloseableHttpClient client, String fileName, byte[] chartImage) { | ||
JSONObject uploadResponse = new JSONObject(); | ||
HttpUriRequest request = RequestBuilder | ||
.get("https://slack.com/api/files.getUploadURLExternal") | ||
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) | ||
.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this header really needed for GET request? |
||
.addParameter("filename", fileName) | ||
.addParameter("length", String.valueOf(chartImage.length)) | ||
.build(); | ||
try (CloseableHttpResponse responseBody = client.execute(request)) { | ||
if (responseBody.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { | ||
uploadResponse = new JSONObject(EntityUtils.toString(responseBody.getEntity())); | ||
} | ||
} catch (IOException | UnsupportedOperationException e) { | ||
log.error("Error getting upload URL", e); | ||
} | ||
return uploadResponse; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package guru.qa.allure.notifications.clients.slack.model; | ||
|
||
import com.google.gson.annotations.SerializedName; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Data | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class ImageBlock implements LayoutBlock { | ||
public static final String TYPE = "image"; | ||
private final String type = TYPE; | ||
private String fallback; | ||
|
||
@SerializedName("image_url") | ||
private String imageUrl; | ||
|
||
@SerializedName("alt_text") | ||
private String altText; | ||
private String title; | ||
private String blockId; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package guru.qa.allure.notifications.clients.slack.model; | ||
|
||
public interface LayoutBlock { | ||
String getType(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package guru.qa.allure.notifications.clients.slack.model; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.util.List; | ||
|
||
@Data | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class SectionBlock implements LayoutBlock { | ||
public static final String TYPE = "section"; | ||
private final String type = TYPE; | ||
private TextObject text; | ||
private List<TextObject> fields; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package guru.qa.allure.notifications.clients.slack.model; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Data | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class TextObject { | ||
public static final String TYPE = "mrkdwn"; | ||
private final String type = TYPE; | ||
private String text; | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make sure to rebase the branch There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not fixed, please rebase your branch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrap into
MessagingException
and re-throw it