From c4e1eac08930582162b0bcc78e532e0cb19ff2f3 Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Sun, 25 Aug 2024 13:17:58 +0300 Subject: [PATCH 01/13] add slack upload flow, up java version to java 11 --- .github/workflows/build.yml | 2 +- .../clients/slack/SlackClient.java | 142 +++++++++++++++--- .../clients/slack/model/ImageBlock.java | 26 ++++ .../clients/slack/model/LayoutBlock.java | 5 + .../clients/slack/model/SectionBlock.java | 19 +++ .../clients/slack/model/TextObject.java | 16 ++ .../src/main/resources/log4j2.xml | 12 ++ build.gradle | 6 +- 8 files changed, 205 insertions(+), 23 deletions(-) create mode 100644 allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/ImageBlock.java create mode 100644 allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/LayoutBlock.java create mode 100644 allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/SectionBlock.java create mode 100644 allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/TextObject.java create mode 100644 allure-notifications/src/main/resources/log4j2.xml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00a8ea31..b615ff29 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11, 17, 21, 22] + java: [11, 17, 21, 22] fail-fast: false steps: diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 13b9af01..4e2e78d7 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -1,15 +1,34 @@ 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.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.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; +@Slf4j public class SlackClient implements Notifier { private final Slack slack; @@ -19,27 +38,116 @@ 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())); + postMessage(messageData); + } + + @Override + @SneakyThrows + public void sendPhoto(MessageData messageData, byte[] chartImage) { + String title = "chart.png"; + JSONObject uploadResponse = getUploadURLExternal(title, chartImage); + + String fileId = uploadResponse.getString("file_id"); + String uploadUrl = uploadResponse.getString("upload_url"); + + try (CloseableHttpClient client = HttpClients.createDefault()) { + 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"); + } + } catch (IOException e) { + log.error("Failed to upload file to Slack", e); + } + + //todo wait until file processing to complete + Thread.sleep(2000); + + JSONObject completeResponse = completeUploadExternal(fileId, title); + + String filePermalink = completeResponse.getJSONArray("files").getJSONObject(0).getString("permalink"); + + postMessage(messageData, filePermalink); + } + + private void postMessage(MessageData messageData) { + postMessage(messageData, ""); + } - Unirest.post("https://slack.com/api/chat.postMessage") + private void postMessage(MessageData messageData, String fileUrl) { + if (fileUrl.isEmpty()) { + Unirest.post("https://slack.com/api/chat.postMessage") + .header("Authorization", "Bearer " + slack.getToken()) + .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) + .body(getTextData(messageData)) + .asString() + .getBody(); + } else { + Unirest.post("https://slack.com/api/chat.postMessage") + .header("Authorization", "Bearer " + slack.getToken()) + .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) + .queryString("channel", slack.getChat()) + .queryString("blocks", getBlocksForPostMessage(messageData, fileUrl)) + .asString() + .getBody(); + } + } + + private boolean uploadFile(String uploadUrl, MultipartEntityBuilder multipartEntityBuilder, CloseableHttpClient client) throws IOException { + HttpUriRequest request = RequestBuilder + .post(uploadUrl) + .setEntity(multipartEntityBuilder.build()) + .addHeader("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)); + } + + 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(List.of(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(String fileId, String title) { + JSONArray array = new JSONArray(); + JSONObject node = new JSONObject(); + node.put("id", fileId); + node.put("title", title); + array.put(node); + + return Unirest.post("https://slack.com/api/files.completeUploadExternal") .header("Authorization", "Bearer " + slack.getToken()) - .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) - .body(body) - .asString() - .getBody(); + .queryString("files", array) + .asJson().getBody().getObject(); } - @Override - public void sendPhoto(MessageData messageData, byte[] chartImage) throws MessagingException { - Unirest.post("https://slack.com/api/files.upload") + private JSONObject getUploadURLExternal(String fileName, byte[] chartImage) { + return Unirest.get("https://slack.com/api/files.getUploadURLExternal") .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(); + .queryString("filename", fileName) + .queryString("length", chartImage.length) + .asJson().getBody().getObject(); } } diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/ImageBlock.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/ImageBlock.java new file mode 100644 index 00000000..5f05f82c --- /dev/null +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/ImageBlock.java @@ -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; +} + diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/LayoutBlock.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/LayoutBlock.java new file mode 100644 index 00000000..9e5de6c4 --- /dev/null +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/LayoutBlock.java @@ -0,0 +1,5 @@ +package guru.qa.allure.notifications.clients.slack.model; + +public interface LayoutBlock { + String getType(); +} diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/SectionBlock.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/SectionBlock.java new file mode 100644 index 00000000..977b187f --- /dev/null +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/SectionBlock.java @@ -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 fields; +} diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/TextObject.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/TextObject.java new file mode 100644 index 00000000..853ea0bc --- /dev/null +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/TextObject.java @@ -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; +} diff --git a/allure-notifications/src/main/resources/log4j2.xml b/allure-notifications/src/main/resources/log4j2.xml new file mode 100644 index 00000000..f79c2e79 --- /dev/null +++ b/allure-notifications/src/main/resources/log4j2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 7d8af422..9e2c2d95 100644 --- a/build.gradle +++ b/build.gradle @@ -6,11 +6,7 @@ allprojects { subprojects { apply plugin: 'java-library' - sourceCompatibility = 1.8 - - tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - } + sourceCompatibility = 11 } ext { From 76896842fb6d6da40d30619e27ca5723bbd1c9a1 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 22 Aug 2024 11:56:11 +0300 Subject: [PATCH 02/13] Fix Log4J config location (cherry picked from commit 86709315bf878a5024eeb1318afccc55566ba4a1) --- .../src/main/resources/log4j2.xml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 allure-notifications-api/src/main/resources/log4j2.xml diff --git a/allure-notifications-api/src/main/resources/log4j2.xml b/allure-notifications-api/src/main/resources/log4j2.xml deleted file mode 100644 index f79c2e79..00000000 --- a/allure-notifications-api/src/main/resources/log4j2.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - From 1008db3db0507a1f32d34e94f307529bf4fd1802 Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Wed, 28 Aug 2024 14:35:46 +0300 Subject: [PATCH 03/13] move to HttpClient, rollback java 8 build --- .../clients/slack/SlackClient.java | 130 +++++++++++------- build.gradle | 6 +- 2 files changed, 89 insertions(+), 47 deletions(-) diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 4e2e78d7..9ad27b25 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -10,23 +10,26 @@ 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.util.List; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; @Slf4j public class SlackClient implements Notifier { @@ -37,20 +40,24 @@ public SlackClient(Slack slack) { } @Override - public void sendText(MessageData messageData) throws MessagingException { - postMessage(messageData); + 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 @SneakyThrows public void sendPhoto(MessageData messageData, byte[] chartImage) { - String title = "chart.png"; - JSONObject uploadResponse = getUploadURLExternal(title, 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"); + String fileId = uploadResponse.getString("file_id"); + String uploadUrl = uploadResponse.getString("upload_url"); - try (CloseableHttpClient client = HttpClients.createDefault()) { MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create() .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) .addBinaryBody("file", new ByteArrayInputStream(chartImage), org.apache.http.entity.ContentType.DEFAULT_BINARY, "chart"); @@ -58,40 +65,50 @@ public void sendPhoto(MessageData messageData, byte[] chartImage) { if (!uploadFile(uploadUrl, multipartEntityBuilder, client)) { log.error("Failed to upload file to Slack"); } - } catch (IOException e) { - log.error("Failed to upload file to Slack", e); - } - //todo wait until file processing to complete - Thread.sleep(2000); + //todo wait until file processing to complete + Thread.sleep(2000); + + JSONObject completeResponse = completeUploadExternal(client, fileId, title); - JSONObject completeResponse = completeUploadExternal(fileId, title); + String filePermalink = completeResponse.getJSONArray("files").getJSONObject(0).getString("permalink"); - String filePermalink = completeResponse.getJSONArray("files").getJSONObject(0).getString("permalink"); + postMessage(client, messageData, filePermalink); - postMessage(messageData, filePermalink); + } catch (IOException e) { + log.error("Failed to post message with file to Slack", e); + } } - private void postMessage(MessageData messageData) { - postMessage(messageData, ""); + private void postMessage(CloseableHttpClient client, MessageData messageData) throws UnsupportedEncodingException { + postMessage(client, messageData, ""); } - private void postMessage(MessageData messageData, String fileUrl) { + private void postMessage(CloseableHttpClient client, MessageData messageData, String fileUrl) throws UnsupportedEncodingException { + HttpUriRequest request; if (fileUrl.isEmpty()) { - Unirest.post("https://slack.com/api/chat.postMessage") - .header("Authorization", "Bearer " + slack.getToken()) - .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) - .body(getTextData(messageData)) - .asString() - .getBody(); + 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 { - Unirest.post("https://slack.com/api/chat.postMessage") - .header("Authorization", "Bearer " + slack.getToken()) - .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) - .queryString("channel", slack.getChat()) - .queryString("blocks", getBlocksForPostMessage(messageData, fileUrl)) - .asString() - .getBody(); + 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); } } @@ -99,7 +116,7 @@ private boolean uploadFile(String uploadUrl, MultipartEntityBuilder multipartEnt HttpUriRequest request = RequestBuilder .post(uploadUrl) .setEntity(multipartEntityBuilder.build()) - .addHeader("Authorization", "Bearer " + slack.getToken()) + .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) .build(); try (CloseableHttpResponse responseBody = client.execute(request)) { @@ -117,7 +134,7 @@ private String getTextData(MessageData messageData) { 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(List.of(sectionBlock, imageBlock)); + return new Gson().toJson(Arrays.asList(sectionBlock, imageBlock)); } private String proceedMessageData(MessageData messageData) { @@ -130,24 +147,45 @@ private String proceedMessageData(MessageData messageData) { return text; } - private JSONObject completeUploadExternal(String fileId, String title) { + 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); - return Unirest.post("https://slack.com/api/files.completeUploadExternal") - .header("Authorization", "Bearer " + slack.getToken()) - .queryString("files", array) - .asJson().getBody().getObject(); + 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(String fileName, byte[] chartImage) { - return Unirest.get("https://slack.com/api/files.getUploadURLExternal") - .header("Authorization", "Bearer " + slack.getToken()) - .queryString("filename", fileName) - .queryString("length", chartImage.length) - .asJson().getBody().getObject(); + 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()) + .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; } } diff --git a/build.gradle b/build.gradle index 9e2c2d95..7d8af422 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,11 @@ allprojects { subprojects { apply plugin: 'java-library' - sourceCompatibility = 11 + sourceCompatibility = 1.8 + + tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + } } ext { From 2a1947267106e22cacfba8d6a6ba858993b5be0f Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Wed, 28 Aug 2024 14:37:25 +0300 Subject: [PATCH 04/13] rollback java 8 build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b615ff29..00a8ea31 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [11, 17, 21, 22] + java: [8, 11, 17, 21, 22] fail-fast: false steps: From 1344c2390e2c7193ab05ff1a77df227f509dffc7 Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Sat, 21 Sep 2024 12:09:23 +0300 Subject: [PATCH 05/13] fix review, update complete upload file --- .../clients/slack/SlackClient.java | 71 ++++++------------- 1 file changed, 21 insertions(+), 50 deletions(-) diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 9ad27b25..6d48533c 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -1,10 +1,6 @@ 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; @@ -12,24 +8,26 @@ import kong.unirest.ContentType; 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.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; 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.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; @Slf4j public class SlackClient implements Notifier { @@ -40,16 +38,15 @@ public SlackClient(Slack slack) { } @Override - public void sendText(MessageData messageData) { + public void sendText(MessageData messageData) throws MessagingException { try (CloseableHttpClient client = HttpClients.createDefault()) { postMessage(client, messageData); } catch (IOException e) { - log.error("Failed to post message to Slack", e); + throw new MessagingException("Failed to post message to Slack", e); } } @Override - @SneakyThrows public void sendPhoto(MessageData messageData, byte[] chartImage) { try (CloseableHttpClient client = HttpClients.createDefault()) { String title = "chart.png"; @@ -66,42 +63,18 @@ public void sendPhoto(MessageData messageData, byte[] chartImage) { log.error("Failed to upload file to Slack"); } - //todo wait until file processing to complete - Thread.sleep(2000); - - JSONObject completeResponse = completeUploadExternal(client, fileId, title); - - String filePermalink = completeResponse.getJSONArray("files").getJSONObject(0).getString("permalink"); - - postMessage(client, messageData, filePermalink); - + completeUploadExternal(client, fileId, proceedMessageData(messageData)); } catch (IOException e) { log.error("Failed to post message with file to Slack", e); } } 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(); - } + HttpUriRequest request = RequestBuilder + .post("https://slack.com/api/chat.postMessage") + .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) + .setEntity(new UrlEncodedFormEntity(getTextData(messageData))) + .build(); try (CloseableHttpResponse responseBody = client.execute(request)) { if (responseBody.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { @@ -127,14 +100,11 @@ private boolean uploadFile(String uploadUrl, MultipartEntityBuilder multipartEnt return true; } - private String getTextData(MessageData messageData) { - return String.format("channel=%s&text=%s", slack.getChat(), proceedMessageData(messageData)); - } - - 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 List getTextData(MessageData messageData) { + List params = new ArrayList<>(); + params.add(new BasicNameValuePair("channel", slack.getChat())); + params.add(new BasicNameValuePair("text", proceedMessageData(messageData))); + return params; } private String proceedMessageData(MessageData messageData) { @@ -147,18 +117,19 @@ private String proceedMessageData(MessageData messageData) { return text; } - private JSONObject completeUploadExternal(CloseableHttpClient client, String fileId, String title) { + private JSONObject completeUploadExternal(CloseableHttpClient client, String fileId, String messageData) { 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()) + .addParameter("initial_comment", messageData) + .addParameter("channel_id", slack.getChat()) .build(); try (CloseableHttpResponse responseBody = client.execute(request)) { if (responseBody.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { From 8e52ad277e4a65d69535285023e60820ad5611af Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Sat, 21 Sep 2024 12:10:35 +0300 Subject: [PATCH 06/13] fix review --- .../qa/allure/notifications/clients/slack/SlackClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 6d48533c..428a7861 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -47,7 +47,7 @@ public void sendText(MessageData messageData) throws MessagingException { } @Override - public void sendPhoto(MessageData messageData, byte[] chartImage) { + public void sendPhoto(MessageData messageData, byte[] chartImage) throws MessagingException { try (CloseableHttpClient client = HttpClients.createDefault()) { String title = "chart.png"; JSONObject uploadResponse = getUploadURLExternal(client, title, chartImage); @@ -65,7 +65,7 @@ public void sendPhoto(MessageData messageData, byte[] chartImage) { completeUploadExternal(client, fileId, proceedMessageData(messageData)); } catch (IOException e) { - log.error("Failed to post message with file to Slack", e); + throw new MessagingException("Failed to post message with file to Slack", e); } } From 382d73d424477b2863e4c2c6936dcde51c7b0e1a Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Wed, 28 Aug 2024 14:35:46 +0300 Subject: [PATCH 07/13] move to HttpClient, rollback java 8 build --- .../clients/slack/SlackClient.java | 130 +++++++++++------- build.gradle | 6 +- 2 files changed, 89 insertions(+), 47 deletions(-) diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 4e2e78d7..9ad27b25 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -10,23 +10,26 @@ 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.util.List; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; @Slf4j public class SlackClient implements Notifier { @@ -37,20 +40,24 @@ public SlackClient(Slack slack) { } @Override - public void sendText(MessageData messageData) throws MessagingException { - postMessage(messageData); + 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 @SneakyThrows public void sendPhoto(MessageData messageData, byte[] chartImage) { - String title = "chart.png"; - JSONObject uploadResponse = getUploadURLExternal(title, 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"); + String fileId = uploadResponse.getString("file_id"); + String uploadUrl = uploadResponse.getString("upload_url"); - try (CloseableHttpClient client = HttpClients.createDefault()) { MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create() .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) .addBinaryBody("file", new ByteArrayInputStream(chartImage), org.apache.http.entity.ContentType.DEFAULT_BINARY, "chart"); @@ -58,40 +65,50 @@ public void sendPhoto(MessageData messageData, byte[] chartImage) { if (!uploadFile(uploadUrl, multipartEntityBuilder, client)) { log.error("Failed to upload file to Slack"); } - } catch (IOException e) { - log.error("Failed to upload file to Slack", e); - } - //todo wait until file processing to complete - Thread.sleep(2000); + //todo wait until file processing to complete + Thread.sleep(2000); + + JSONObject completeResponse = completeUploadExternal(client, fileId, title); - JSONObject completeResponse = completeUploadExternal(fileId, title); + String filePermalink = completeResponse.getJSONArray("files").getJSONObject(0).getString("permalink"); - String filePermalink = completeResponse.getJSONArray("files").getJSONObject(0).getString("permalink"); + postMessage(client, messageData, filePermalink); - postMessage(messageData, filePermalink); + } catch (IOException e) { + log.error("Failed to post message with file to Slack", e); + } } - private void postMessage(MessageData messageData) { - postMessage(messageData, ""); + private void postMessage(CloseableHttpClient client, MessageData messageData) throws UnsupportedEncodingException { + postMessage(client, messageData, ""); } - private void postMessage(MessageData messageData, String fileUrl) { + private void postMessage(CloseableHttpClient client, MessageData messageData, String fileUrl) throws UnsupportedEncodingException { + HttpUriRequest request; if (fileUrl.isEmpty()) { - Unirest.post("https://slack.com/api/chat.postMessage") - .header("Authorization", "Bearer " + slack.getToken()) - .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) - .body(getTextData(messageData)) - .asString() - .getBody(); + 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 { - Unirest.post("https://slack.com/api/chat.postMessage") - .header("Authorization", "Bearer " + slack.getToken()) - .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) - .queryString("channel", slack.getChat()) - .queryString("blocks", getBlocksForPostMessage(messageData, fileUrl)) - .asString() - .getBody(); + 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); } } @@ -99,7 +116,7 @@ private boolean uploadFile(String uploadUrl, MultipartEntityBuilder multipartEnt HttpUriRequest request = RequestBuilder .post(uploadUrl) .setEntity(multipartEntityBuilder.build()) - .addHeader("Authorization", "Bearer " + slack.getToken()) + .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) .build(); try (CloseableHttpResponse responseBody = client.execute(request)) { @@ -117,7 +134,7 @@ private String getTextData(MessageData messageData) { 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(List.of(sectionBlock, imageBlock)); + return new Gson().toJson(Arrays.asList(sectionBlock, imageBlock)); } private String proceedMessageData(MessageData messageData) { @@ -130,24 +147,45 @@ private String proceedMessageData(MessageData messageData) { return text; } - private JSONObject completeUploadExternal(String fileId, String title) { + 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); - return Unirest.post("https://slack.com/api/files.completeUploadExternal") - .header("Authorization", "Bearer " + slack.getToken()) - .queryString("files", array) - .asJson().getBody().getObject(); + 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(String fileName, byte[] chartImage) { - return Unirest.get("https://slack.com/api/files.getUploadURLExternal") - .header("Authorization", "Bearer " + slack.getToken()) - .queryString("filename", fileName) - .queryString("length", chartImage.length) - .asJson().getBody().getObject(); + 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()) + .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; } } diff --git a/build.gradle b/build.gradle index 9e2c2d95..7d8af422 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,11 @@ allprojects { subprojects { apply plugin: 'java-library' - sourceCompatibility = 11 + sourceCompatibility = 1.8 + + tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + } } ext { From c1848cecf018c893c0f0a35626ff832464e579e6 Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Wed, 28 Aug 2024 14:37:25 +0300 Subject: [PATCH 08/13] rollback java 8 build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b615ff29..00a8ea31 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [11, 17, 21, 22] + java: [8, 11, 17, 21, 22] fail-fast: false steps: From 99f91848b0cd9713d2a136b88400ac692a78380e Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Sat, 21 Sep 2024 12:09:23 +0300 Subject: [PATCH 09/13] fix review, update complete upload file --- .../clients/slack/SlackClient.java | 71 ++++++------------- 1 file changed, 21 insertions(+), 50 deletions(-) diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 9ad27b25..6d48533c 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -1,10 +1,6 @@ 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; @@ -12,24 +8,26 @@ import kong.unirest.ContentType; 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.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; 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.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; @Slf4j public class SlackClient implements Notifier { @@ -40,16 +38,15 @@ public SlackClient(Slack slack) { } @Override - public void sendText(MessageData messageData) { + public void sendText(MessageData messageData) throws MessagingException { try (CloseableHttpClient client = HttpClients.createDefault()) { postMessage(client, messageData); } catch (IOException e) { - log.error("Failed to post message to Slack", e); + throw new MessagingException("Failed to post message to Slack", e); } } @Override - @SneakyThrows public void sendPhoto(MessageData messageData, byte[] chartImage) { try (CloseableHttpClient client = HttpClients.createDefault()) { String title = "chart.png"; @@ -66,42 +63,18 @@ public void sendPhoto(MessageData messageData, byte[] chartImage) { log.error("Failed to upload file to Slack"); } - //todo wait until file processing to complete - Thread.sleep(2000); - - JSONObject completeResponse = completeUploadExternal(client, fileId, title); - - String filePermalink = completeResponse.getJSONArray("files").getJSONObject(0).getString("permalink"); - - postMessage(client, messageData, filePermalink); - + completeUploadExternal(client, fileId, proceedMessageData(messageData)); } catch (IOException e) { log.error("Failed to post message with file to Slack", e); } } 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(); - } + HttpUriRequest request = RequestBuilder + .post("https://slack.com/api/chat.postMessage") + .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) + .setEntity(new UrlEncodedFormEntity(getTextData(messageData))) + .build(); try (CloseableHttpResponse responseBody = client.execute(request)) { if (responseBody.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { @@ -127,14 +100,11 @@ private boolean uploadFile(String uploadUrl, MultipartEntityBuilder multipartEnt return true; } - private String getTextData(MessageData messageData) { - return String.format("channel=%s&text=%s", slack.getChat(), proceedMessageData(messageData)); - } - - 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 List getTextData(MessageData messageData) { + List params = new ArrayList<>(); + params.add(new BasicNameValuePair("channel", slack.getChat())); + params.add(new BasicNameValuePair("text", proceedMessageData(messageData))); + return params; } private String proceedMessageData(MessageData messageData) { @@ -147,18 +117,19 @@ private String proceedMessageData(MessageData messageData) { return text; } - private JSONObject completeUploadExternal(CloseableHttpClient client, String fileId, String title) { + private JSONObject completeUploadExternal(CloseableHttpClient client, String fileId, String messageData) { 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()) + .addParameter("initial_comment", messageData) + .addParameter("channel_id", slack.getChat()) .build(); try (CloseableHttpResponse responseBody = client.execute(request)) { if (responseBody.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { From c54bc683aac40a5648e5b199516da3f6dbea84af Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Sat, 21 Sep 2024 12:10:35 +0300 Subject: [PATCH 10/13] fix review --- .../qa/allure/notifications/clients/slack/SlackClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 6d48533c..428a7861 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -47,7 +47,7 @@ public void sendText(MessageData messageData) throws MessagingException { } @Override - public void sendPhoto(MessageData messageData, byte[] chartImage) { + public void sendPhoto(MessageData messageData, byte[] chartImage) throws MessagingException { try (CloseableHttpClient client = HttpClients.createDefault()) { String title = "chart.png"; JSONObject uploadResponse = getUploadURLExternal(client, title, chartImage); @@ -65,7 +65,7 @@ public void sendPhoto(MessageData messageData, byte[] chartImage) { completeUploadExternal(client, fileId, proceedMessageData(messageData)); } catch (IOException e) { - log.error("Failed to post message with file to Slack", e); + throw new MessagingException("Failed to post message with file to Slack", e); } } From 93058bdf22961851c50807280943f9707df23214 Mon Sep 17 00:00:00 2001 From: "m.liakhavets" Date: Tue, 24 Sep 2024 12:41:39 +0300 Subject: [PATCH 11/13] remove unnecessary slack model --- .../clients/slack/model/ImageBlock.java | 26 ------------------- .../clients/slack/model/LayoutBlock.java | 5 ---- .../clients/slack/model/SectionBlock.java | 19 -------------- .../clients/slack/model/TextObject.java | 16 ------------ 4 files changed, 66 deletions(-) delete mode 100644 allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/ImageBlock.java delete mode 100644 allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/LayoutBlock.java delete mode 100644 allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/SectionBlock.java delete mode 100644 allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/TextObject.java diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/ImageBlock.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/ImageBlock.java deleted file mode 100644 index 5f05f82c..00000000 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/ImageBlock.java +++ /dev/null @@ -1,26 +0,0 @@ -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; -} - diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/LayoutBlock.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/LayoutBlock.java deleted file mode 100644 index 9e5de6c4..00000000 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/LayoutBlock.java +++ /dev/null @@ -1,5 +0,0 @@ -package guru.qa.allure.notifications.clients.slack.model; - -public interface LayoutBlock { - String getType(); -} diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/SectionBlock.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/SectionBlock.java deleted file mode 100644 index 977b187f..00000000 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/SectionBlock.java +++ /dev/null @@ -1,19 +0,0 @@ -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 fields; -} diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/TextObject.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/TextObject.java deleted file mode 100644 index 853ea0bc..00000000 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/model/TextObject.java +++ /dev/null @@ -1,16 +0,0 @@ -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; -} From a7ddf200126047827dfa551d015362cd121efc12 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Sep 2024 14:36:08 +0300 Subject: [PATCH 12/13] Polishing --- .../clients/slack/SlackClient.java | 164 +++++++----------- .../exceptions/MessageSendException.java | 4 + .../exceptions/MessagingException.java | 4 + 3 files changed, 66 insertions(+), 106 deletions(-) diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 428a7861..3b1960ec 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -2,13 +2,12 @@ import guru.qa.allure.notifications.clients.Notifier; import guru.qa.allure.notifications.config.slack.Slack; +import guru.qa.allure.notifications.exceptions.MessageBuildException; +import guru.qa.allure.notifications.exceptions.MessageSendException; 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.json.JSONArray; import kong.unirest.json.JSONObject; -import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpHeaders; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; @@ -16,6 +15,7 @@ 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.ContentType; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; @@ -23,13 +23,11 @@ import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -@Slf4j public class SlackClient implements Notifier { private final Slack slack; @@ -39,124 +37,78 @@ public SlackClient(Slack slack) { @Override public void sendText(MessageData messageData) throws MessagingException { + List params = new ArrayList<>(); + params.add(new BasicNameValuePair("channel", slack.getChat())); + params.add(new BasicNameValuePair("text", createMessage(messageData))); + + String errorDescription = "Failed to post message to Slack"; try (CloseableHttpClient client = HttpClients.createDefault()) { - postMessage(client, messageData); + HttpUriRequest request = RequestBuilder + .post("https://slack.com/api/chat.postMessage") + .setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8)) + .build(); + executeRequest(client, request, errorDescription); } catch (IOException e) { - throw new MessagingException("Failed to post message to Slack", e); + throw new MessageSendException(errorDescription, e); } } @Override public void sendPhoto(MessageData messageData, byte[] chartImage) throws MessagingException { 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"); - } - - completeUploadExternal(client, fileId, proceedMessageData(messageData)); + HttpUriRequest uploadUrlRequest = RequestBuilder + .get("https://slack.com/api/files.getUploadURLExternal") + .addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()) + .addParameter("filename", "chart.png") + .addParameter("length", String.valueOf(chartImage.length)) + .build(); + JSONObject uploadUrlResponse = new JSONObject( + executeRequest(client, uploadUrlRequest, "Error getting upload URL")); + + String fileId = uploadUrlResponse.getString("file_id"); + String uploadUrl = uploadUrlResponse.getString("upload_url"); + + HttpUriRequest request = RequestBuilder + .post(uploadUrl) + .setEntity(MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addBinaryBody("file", chartImage, ContentType.DEFAULT_BINARY, "chart") + .build()) + .build(); + executeRequest(client, request, "Failed to upload file to Slack"); + + HttpUriRequest completeUploadRequest = RequestBuilder + .post("https://slack.com/api/files.completeUploadExternal") + .addParameter("files", "[{\"id\":\"" + fileId + "\"}]") + .addParameter("initial_comment", createMessage(messageData)) + .addParameter("channel_id", slack.getChat()) + .build(); + executeRequest(client, completeUploadRequest, "Error complete upload file"); } catch (IOException e) { - throw new MessagingException("Failed to post message with file to Slack", e); - } - } - - private void postMessage(CloseableHttpClient client, MessageData messageData) throws UnsupportedEncodingException { - HttpUriRequest request = RequestBuilder - .post("https://slack.com/api/chat.postMessage") - .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) - .setEntity(new UrlEncodedFormEntity(getTextData(messageData))) - .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; - } + throw new MessageSendException("Failed to post message with file to Slack", e); } - return true; } - private List getTextData(MessageData messageData) { - List params = new ArrayList<>(); - params.add(new BasicNameValuePair("channel", slack.getChat())); - params.add(new BasicNameValuePair("text", proceedMessageData(messageData))); - return params; + private String createMessage(MessageData messageData) throws MessageBuildException { + return new MessageTemplate(messageData).createMessageFromTemplate(slack.getTemplatePath()); } - 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 messageData) { - JSONObject completeUploadResponse = new JSONObject(); - JSONArray array = new JSONArray(); - JSONObject node = new JSONObject(); - node.put("id", fileId); - array.put(node); + private String executeRequest(CloseableHttpClient client, HttpUriRequest request, String errorDescription) + throws MessageSendException { + request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()); - HttpUriRequest request = RequestBuilder - .post("https://slack.com/api/files.completeUploadExternal") - .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken()) - .addParameter("files", array.toString()) - .addParameter("initial_comment", messageData) - .addParameter("channel_id", slack.getChat()) - .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; - } + int statusCode = responseBody.getStatusLine().getStatusCode(); + String responseAsString = EntityUtils.toString(responseBody.getEntity()); - 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()) - .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())); + if (statusCode == HttpStatus.SC_OK) { + return responseAsString; } - } catch (IOException | UnsupportedOperationException e) { - log.error("Error getting upload URL", e); + throw new MessageSendException( + String.format("%s. HTTP status code: %d, HTTP response: %s", errorDescription, statusCode, + responseAsString)); + } catch (IOException e) { + throw new MessageSendException(errorDescription, e); } - return uploadResponse; } } diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/exceptions/MessageSendException.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/exceptions/MessageSendException.java index 55c2a7dc..5e3efa1e 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/exceptions/MessageSendException.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/exceptions/MessageSendException.java @@ -4,4 +4,8 @@ public class MessageSendException extends MessagingException { public MessageSendException(String message, Throwable cause) { super(message, cause); } + + public MessageSendException(String message) { + super(message); + } } diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/exceptions/MessagingException.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/exceptions/MessagingException.java index 43690c67..81b2c0d3 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/exceptions/MessagingException.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/exceptions/MessagingException.java @@ -4,4 +4,8 @@ public class MessagingException extends Exception { public MessagingException(String message, Throwable cause) { super(message, cause); } + + public MessagingException(String message) { + super(message); + } } From 33d05eb0b72615e383bee7fc0ad15ae79055b24a Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Sep 2024 18:43:32 +0300 Subject: [PATCH 13/13] Add common method to post form data --- .../clients/slack/SlackClient.java | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java index 3b1960ec..10e3a734 100644 --- a/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java +++ b/allure-notifications-api/src/main/java/guru/qa/allure/notifications/clients/slack/SlackClient.java @@ -37,17 +37,13 @@ public SlackClient(Slack slack) { @Override public void sendText(MessageData messageData) throws MessagingException { - List params = new ArrayList<>(); - params.add(new BasicNameValuePair("channel", slack.getChat())); - params.add(new BasicNameValuePair("text", createMessage(messageData))); - String errorDescription = "Failed to post message to Slack"; try (CloseableHttpClient client = HttpClients.createDefault()) { - HttpUriRequest request = RequestBuilder - .post("https://slack.com/api/chat.postMessage") - .setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8)) - .build(); - executeRequest(client, request, errorDescription); + List postMessageFormData = new ArrayList<>(); + postMessageFormData.add(new BasicNameValuePair("channel", slack.getChat())); + postMessageFormData.add(new BasicNameValuePair("text", createMessage(messageData))); + + executeRequest(client, "https://slack.com/api/chat.postMessage", postMessageFormData, errorDescription); } catch (IOException e) { throw new MessageSendException(errorDescription, e); } @@ -58,7 +54,6 @@ public void sendPhoto(MessageData messageData, byte[] chartImage) throws Messagi try (CloseableHttpClient client = HttpClients.createDefault()) { HttpUriRequest uploadUrlRequest = RequestBuilder .get("https://slack.com/api/files.getUploadURLExternal") - .addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()) .addParameter("filename", "chart.png") .addParameter("length", String.valueOf(chartImage.length)) .build(); @@ -68,22 +63,22 @@ public void sendPhoto(MessageData messageData, byte[] chartImage) throws Messagi String fileId = uploadUrlResponse.getString("file_id"); String uploadUrl = uploadUrlResponse.getString("upload_url"); - HttpUriRequest request = RequestBuilder + HttpUriRequest uploadFileRequest = RequestBuilder .post(uploadUrl) .setEntity(MultipartEntityBuilder.create() .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) .addBinaryBody("file", chartImage, ContentType.DEFAULT_BINARY, "chart") .build()) .build(); - executeRequest(client, request, "Failed to upload file to Slack"); + executeRequest(client, uploadFileRequest, "Failed to upload file to Slack"); - HttpUriRequest completeUploadRequest = RequestBuilder - .post("https://slack.com/api/files.completeUploadExternal") - .addParameter("files", "[{\"id\":\"" + fileId + "\"}]") - .addParameter("initial_comment", createMessage(messageData)) - .addParameter("channel_id", slack.getChat()) - .build(); - executeRequest(client, completeUploadRequest, "Error complete upload file"); + List completeUploadFormData = new ArrayList<>(); + completeUploadFormData.add(new BasicNameValuePair("files", "[{\"id\":\"" + fileId + "\"}]")); + completeUploadFormData.add(new BasicNameValuePair("initial_comment", createMessage(messageData))); + completeUploadFormData.add(new BasicNameValuePair("channel_id", slack.getChat())); + + executeRequest(client, "https://slack.com/api/files.completeUploadExternal", completeUploadFormData, + "Error complete upload file"); } catch (IOException e) { throw new MessageSendException("Failed to post message with file to Slack", e); } @@ -93,6 +88,15 @@ private String createMessage(MessageData messageData) throws MessageBuildExcepti return new MessageTemplate(messageData).createMessageFromTemplate(slack.getTemplatePath()); } + private void executeRequest(CloseableHttpClient client, String uri, List formData, + String errorDescription) throws MessageSendException { + HttpUriRequest request = RequestBuilder + .post(uri) + .setEntity(new UrlEncodedFormEntity(formData, StandardCharsets.UTF_8)) + .build(); + executeRequest(client, request, errorDescription); + } + private String executeRequest(CloseableHttpClient client, HttpUriRequest request, String errorDescription) throws MessageSendException { request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + slack.getToken());