From 502310634a86aad4eaf583fb7e45d29cdefff5d9 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Fri, 28 Aug 2026 16:21:39 -0400 Subject: [PATCH] feat(model): add TaskCardBlock and UrlSourceElement --- .../com/slack/api/model/block/Blocks.java | 6 ++ .../slack/api/model/block/TaskCardBlock.java | 32 ++++++++ .../model/block/element/UrlSourceElement.java | 21 +++++ .../api/util/json/GsonLayoutBlockFactory.java | 2 + .../api/model/block/BlockKitTest.java | 76 +++++++++++++++++++ .../api/model/block/BlocksTest.java | 5 ++ 6 files changed, 142 insertions(+) create mode 100644 slack-api-model/src/main/java/com/slack/api/model/block/TaskCardBlock.java create mode 100644 slack-api-model/src/main/java/com/slack/api/model/block/element/UrlSourceElement.java diff --git a/slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java b/slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java index 9abe88458..01352cafa 100644 --- a/slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java +++ b/slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java @@ -147,4 +147,10 @@ public static CarouselBlock carousel(ModelConfigurator configurator) { + return configurator.configure(TaskCardBlock.builder()).build(); + } + } diff --git a/slack-api-model/src/main/java/com/slack/api/model/block/TaskCardBlock.java b/slack-api-model/src/main/java/com/slack/api/model/block/TaskCardBlock.java new file mode 100644 index 000000000..5400d2f4f --- /dev/null +++ b/slack-api-model/src/main/java/com/slack/api/model/block/TaskCardBlock.java @@ -0,0 +1,32 @@ +package com.slack.api.model.block; + +import com.slack.api.model.block.composition.SlackIconObject; +import com.slack.api.model.block.element.UrlSourceElement; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * https://docs.slack.dev/reference/block-kit/blocks/task-card-block + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TaskCardBlock implements LayoutBlock { + public static final String TYPE = "task_card"; + private final String type = TYPE; + + private String taskId; + private String title; + private String status; + private RichTextBlock details; + private RichTextBlock output; + private List sources; + private String blockId; + private SlackIconObject icon; + private Boolean hideTitle; +} diff --git a/slack-api-model/src/main/java/com/slack/api/model/block/element/UrlSourceElement.java b/slack-api-model/src/main/java/com/slack/api/model/block/element/UrlSourceElement.java new file mode 100644 index 000000000..5e60c9799 --- /dev/null +++ b/slack-api-model/src/main/java/com/slack/api/model/block/element/UrlSourceElement.java @@ -0,0 +1,21 @@ +package com.slack.api.model.block.element; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * https://docs.slack.dev/reference/block-kit/block-elements/url-source-element + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UrlSourceElement { + public static final String TYPE = "url"; + private final String type = TYPE; + + private String url; + private String text; +} diff --git a/slack-api-model/src/main/java/com/slack/api/util/json/GsonLayoutBlockFactory.java b/slack-api-model/src/main/java/com/slack/api/util/json/GsonLayoutBlockFactory.java index d97b36d28..2917d0823 100644 --- a/slack-api-model/src/main/java/com/slack/api/util/json/GsonLayoutBlockFactory.java +++ b/slack-api-model/src/main/java/com/slack/api/util/json/GsonLayoutBlockFactory.java @@ -67,6 +67,8 @@ private Class getLayoutClassInstance(String typeName) { return CardBlock.class; case CarouselBlock.TYPE: return CarouselBlock.class; + case TaskCardBlock.TYPE: + return TaskCardBlock.class; default: if (failOnUnknownProperties) { throw new JsonParseException("Unsupported layout block type: " + typeName); diff --git a/slack-api-model/src/test/java/test_locally/api/model/block/BlockKitTest.java b/slack-api-model/src/test/java/test_locally/api/model/block/BlockKitTest.java index f398c781f..60d7f2bf0 100644 --- a/slack-api-model/src/test/java/test_locally/api/model/block/BlockKitTest.java +++ b/slack-api-model/src/test/java/test_locally/api/model/block/BlockKitTest.java @@ -2202,4 +2202,80 @@ public void buildCarouselBlock() { assertThat(parsedCarousel.getElements().get(0).getBlockId(), is("card1")); assertThat(parsedCarousel.getElements().get(0).getActions().size(), is(1)); } + + @Test + public void parseTaskCardBlock() { + String json = "{\n" + + " \"blocks\": [\n" + + " {\n" + + " \"type\": \"task_card\",\n" + + " \"task_id\": \"task_1\",\n" + + " \"title\": \"Fetching weather data\",\n" + + " \"status\": \"pending\",\n" + + " \"output\": {\n" + + " \"type\": \"rich_text\",\n" + + " \"elements\": [\n" + + " {\n" + + " \"type\": \"rich_text_section\",\n" + + " \"elements\": [\n" + + " {\n" + + " \"type\": \"text\",\n" + + " \"text\": \"Found weather data for Chicago from 2 sources\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"sources\": [\n" + + " {\n" + + " \"type\": \"url\",\n" + + " \"url\": \"https://weather.com/\",\n" + + " \"text\": \"weather.com\"\n" + + " },\n" + + " {\n" + + " \"type\": \"url\",\n" + + " \"url\": \"https://www.accuweather.com/\",\n" + + " \"text\": \"accuweather.com\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + Gson gson = GsonFactory.createSnakeCase(); + Message message = gson.fromJson(json, Message.class); + assertThat(message, is(notNullValue())); + assertThat(message.getBlocks().size(), is(1)); + + TaskCardBlock taskCard = (TaskCardBlock) message.getBlocks().get(0); + assertThat(taskCard.getType(), is("task_card")); + assertThat(taskCard.getTaskId(), is("task_1")); + assertThat(taskCard.getTitle(), is("Fetching weather data")); + assertThat(taskCard.getStatus(), is("pending")); + + assertThat(taskCard.getOutput(), is(notNullValue())); + assertThat(taskCard.getOutput().getType(), is("rich_text")); + + assertThat(taskCard.getSources().size(), is(2)); + assertThat(taskCard.getSources().get(0).getType(), is("url")); + assertThat(taskCard.getSources().get(0).getUrl(), is("https://weather.com/")); + assertThat(taskCard.getSources().get(0).getText(), is("weather.com")); + assertThat(taskCard.getSources().get(1).getUrl(), is("https://www.accuweather.com/")); + assertThat(taskCard.getSources().get(1).getText(), is("accuweather.com")); + } + + @Test + public void buildTaskCardBlock() { + TaskCardBlock taskCard = Blocks.taskCard(t -> t + .taskId("task_42") + .title("Processing data") + .status("in_progress") + .sources(Arrays.asList( + UrlSourceElement.builder().url("https://example.com").text("example").build()))); + assertThat(taskCard, is(notNullValue())); + assertThat(taskCard.getType(), is("task_card")); + assertThat(taskCard.getTaskId(), is("task_42")); + assertThat(taskCard.getTitle(), is("Processing data")); + assertThat(taskCard.getStatus(), is("in_progress")); + assertThat(taskCard.getSources().size(), is(1)); + } } diff --git a/slack-api-model/src/test/java/test_locally/api/model/block/BlocksTest.java b/slack-api-model/src/test/java/test_locally/api/model/block/BlocksTest.java index 332ee593b..4fe2cd8be 100644 --- a/slack-api-model/src/test/java/test_locally/api/model/block/BlocksTest.java +++ b/slack-api-model/src/test/java/test_locally/api/model/block/BlocksTest.java @@ -117,6 +117,11 @@ public void testCarousel() { ))), is(notNullValue())); } + @Test + public void testTaskCard() { + assertThat(taskCard(t -> t.taskId("task_1").title("Do thing").status("complete")), is(notNullValue())); + } + @Test public void testImage() { assertThat(Blocks.image(i -> i.blockId("block-id").imageUrl("https://www.example.com/")), is(notNullValue()));