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 7db73638e..46a22b4f8 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 @@ -165,6 +165,12 @@ public static DataTableBlock dataTable(ModelConfigurator configurator) { + return configurator.configure(TableBlock.builder()).build(); + } + // TaskCardBlock public static TaskCardBlock taskCard(ModelConfigurator configurator) { diff --git a/slack-api-model/src/main/java/com/slack/api/model/block/TableBlock.java b/slack-api-model/src/main/java/com/slack/api/model/block/TableBlock.java new file mode 100644 index 000000000..b9f41e28e --- /dev/null +++ b/slack-api-model/src/main/java/com/slack/api/model/block/TableBlock.java @@ -0,0 +1,64 @@ +package com.slack.api.model.block; + +import com.slack.api.model.block.composition.TableCell; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Displays structured information in a table. + * + * @see Table block + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TableBlock implements LayoutBlock { + public static final String TYPE = "table"; + private final String type = TYPE; + + /** + * An array consisting of table rows. Maximum 100 rows. Each row object is an array with a max of 20 table + * cells. Table cells can have a type of {@code rich_text}, {@code raw_text}, or {@code raw_number}. + */ + @Builder.Default + private List> rows = new ArrayList<>(); + + /** + * An array describing column behavior. If there are fewer items in the {@code column_settings} array than + * there are columns in the table, then the items in the the {@code column_settings} array will describe the + * same number of columns in the table as there are in the array itself. Any additional columns will have the + * default behavior. Maximum 20 items. + */ + private List columnSettings; + + private String blockId; + + /** + * Describes the behavior of a column in a {@link TableBlock}. + * + * @see Schema for column_settings + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ColumnSetting { + + /** + * The alignment for items in this column. Can be {@code left}, {@code center}, or {@code right}. Defaults to + * {@code left} if not defined. + */ + private String align; + + /** + * Whether the contents of this column should be wrapped or not. Defaults to {@code false} if not defined. + */ + private Boolean isWrapped; + } +} diff --git a/slack-api-model/src/main/java/com/slack/api/model/block/composition/RawNumberObject.java b/slack-api-model/src/main/java/com/slack/api/model/block/composition/RawNumberObject.java index 5c845eb09..8a71bf4a1 100644 --- a/slack-api-model/src/main/java/com/slack/api/model/block/composition/RawNumberObject.java +++ b/slack-api-model/src/main/java/com/slack/api/model/block/composition/RawNumberObject.java @@ -8,6 +8,7 @@ /** * Defines an object containing a numeric value. * + * @see Table block * @see Data table block */ @Data diff --git a/slack-api-model/src/main/java/com/slack/api/model/block/composition/RawTextObject.java b/slack-api-model/src/main/java/com/slack/api/model/block/composition/RawTextObject.java index 6672aefff..aa3a1a6f5 100644 --- a/slack-api-model/src/main/java/com/slack/api/model/block/composition/RawTextObject.java +++ b/slack-api-model/src/main/java/com/slack/api/model/block/composition/RawTextObject.java @@ -8,6 +8,7 @@ /** * Defines an object containing some text. * + * @see Table block * @see Data table block */ @Data diff --git a/slack-api-model/src/main/java/com/slack/api/model/block/composition/TableCell.java b/slack-api-model/src/main/java/com/slack/api/model/block/composition/TableCell.java index 283a6ecb4..bed45dc8e 100644 --- a/slack-api-model/src/main/java/com/slack/api/model/block/composition/TableCell.java +++ b/slack-api-model/src/main/java/com/slack/api/model/block/composition/TableCell.java @@ -10,6 +10,7 @@ *
  • {@link com.slack.api.model.block.RichTextBlock RichTextBlock} ({@code rich_text})
  • * * + * @see Table block * @see Data table block */ public interface TableCell { 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 f4e8dc3f8..2c7a89454 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 @@ -71,6 +71,8 @@ private Class getLayoutClassInstance(String typeName) { return SectionBlock.class; case ShareShortcutBlock.TYPE: return ShareShortcutBlock.class; + case TableBlock.TYPE: + return TableBlock.class; case TaskCardBlock.TYPE: return TaskCardBlock.class; case VideoBlock.TYPE: diff --git a/slack-api-model/src/main/java/com/slack/api/util/json/GsonTableCellFactory.java b/slack-api-model/src/main/java/com/slack/api/util/json/GsonTableCellFactory.java index 6963f79bb..969ef9a82 100644 --- a/slack-api-model/src/main/java/com/slack/api/util/json/GsonTableCellFactory.java +++ b/slack-api-model/src/main/java/com/slack/api/util/json/GsonTableCellFactory.java @@ -12,8 +12,10 @@ /** * Factory for deserializing the cells of a + * {@link com.slack.api.model.block.TableBlock table block} or a * {@link com.slack.api.model.block.DataTableBlock data table block}. * + * @see Table block * @see Data table block */ public class GsonTableCellFactory implements JsonDeserializer, JsonSerializer { 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 ed23d8d8a..d8456cdd0 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 @@ -2,6 +2,7 @@ import com.google.gson.Gson; import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; import com.slack.api.model.Message; import com.slack.api.model.block.*; import com.slack.api.model.block.composition.ActionCellObject; @@ -2710,4 +2711,156 @@ public void buildAuthProviderBlock() { "\"name\":{\"type\":\"plain_text\",\"text\":\"Acme\"}," + "\"is_connected\":false,\"auth_url\":\"https://acme.example.com/oauth\"}")); } + + @Test + public void parseTableBlock() { + // https://docs.slack.dev/reference/block-kit/blocks/table-block + String json = "{\n" + + " \"blocks\": [\n" + + " {\n" + + " \"type\": \"table\",\n" + + " \"column_settings\": [\n" + + " {\n" + + " \"is_wrapped\": true\n" + + " },\n" + + " {\n" + + " \"align\": \"right\"\n" + + " }\n" + + " ],\n" + + " \"rows\": [\n" + + " [\n" + + " {\n" + + " \"type\": \"raw_text\",\n" + + " \"text\": \"Header A\"\n" + + " },\n" + + " {\n" + + " \"type\": \"raw_text\",\n" + + " \"text\": \"Header B\"\n" + + " }\n" + + " ],\n" + + " [\n" + + " {\n" + + " \"type\": \"raw_text\",\n" + + " \"text\": \"Data 1A\"\n" + + " },\n" + + " {\n" + + " \"type\": \"rich_text\",\n" + + " \"elements\": [\n" + + " {\n" + + " \"type\": \"rich_text_section\",\n" + + " \"elements\": [\n" + + " {\n" + + " \"text\": \"Data 1B\",\n" + + " \"type\": \"link\",\n" + + " \"url\": \"https://slack.com\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " [\n" + + " {\n" + + " \"type\": \"raw_text\",\n" + + " \"text\": \"Data 2A\"\n" + + " },\n" + + " {\n" + + " \"type\": \"rich_text\",\n" + + " \"elements\": [\n" + + " {\n" + + " \"type\": \"rich_text_section\",\n" + + " \"elements\": [\n" + + " {\n" + + " \"text\": \"Data 2B\",\n" + + " \"type\": \"link\",\n" + + " \"url\": \"https://slack.com\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " [\n" + + " {\n" + + " \"type\": \"raw_text\",\n" + + " \"text\": \"Data 3A\"\n" + + " },\n" + + " {\n" + + " \"type\": \"raw_number\",\n" + + " \"value\": 400.5,\n" + + " \"text\": \"$400.50\"\n" + + " }\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)); + + TableBlock block = (TableBlock) message.getBlocks().get(0); + assertThat(block.getType(), is("table")); + assertThat(block.getColumnSettings().size(), is(2)); + assertThat(block.getColumnSettings().get(0).getAlign(), is(nullValue())); + assertThat(block.getColumnSettings().get(0).getIsWrapped(), is(true)); + assertThat(block.getColumnSettings().get(1).getAlign(), is("right")); + assertThat(block.getColumnSettings().get(1).getIsWrapped(), is(nullValue())); + assertThat(block.getRows().size(), is(4)); + + RawTextObject header = (RawTextObject) block.getRows().get(0).get(0); + assertThat(header.getType(), is("raw_text")); + assertThat(header.getText(), is("Header A")); + + RichTextBlock richCell = (RichTextBlock) block.getRows().get(1).get(1); + assertThat(richCell.getType(), is("rich_text")); + RichTextSectionElement section = (RichTextSectionElement) richCell.getElements().get(0); + RichTextSectionElement.Link link = (RichTextSectionElement.Link) section.getElements().get(0); + assertThat(link.getText(), is("Data 1B")); + assertThat(link.getUrl(), is("https://slack.com")); + + RawNumberObject numberCell = (RawNumberObject) block.getRows().get(3).get(1); + assertThat(numberCell.getType(), is("raw_number")); + assertThat(numberCell.getValue(), is(400.5)); + assertThat(numberCell.getText(), is("$400.50")); + + assertThat( + gson.toJsonTree(message.getBlocks()), + is(JsonParser.parseString(json).getAsJsonObject().get("blocks"))); + } + + @Test + public void buildTableBlock() { + TableBlock block = table(t -> t + .blockId("bid") + .columnSettings(Arrays.asList( + TableBlock.ColumnSetting.builder().isWrapped(true).build(), + TableBlock.ColumnSetting.builder().align("right").build() + )) + .rows(Arrays.asList( + Arrays.asList( + RawTextObject.builder().text("Item").build(), + RawTextObject.builder().text("Cost").build() + ), + Arrays.asList( + RawTextObject.builder().text("Rent").build(), + RawNumberObject.builder().value(400.5).text("$400.50").build() + ) + ))); + assertThat(block, is(notNullValue())); + + Gson gson = GsonFactory.createSnakeCase(); + String json = gson.toJson(block); + assertThat(json, is("{\"type\":\"table\"," + + "\"rows\":[" + + "[{\"type\":\"raw_text\",\"text\":\"Item\"},{\"type\":\"raw_text\",\"text\":\"Cost\"}]," + + "[{\"type\":\"raw_text\",\"text\":\"Rent\"},{\"type\":\"raw_number\",\"value\":400.5,\"text\":\"$400.50\"}]" + + "]," + + "\"column_settings\":[{\"is_wrapped\":true},{\"align\":\"right\"}]," + + "\"block_id\":\"bid\"}")); + + TableBlock restored = (TableBlock) gson.fromJson(json, LayoutBlock.class); + assertThat(restored, is(block)); + } }