Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.slack.api.model.block.ContextBlockElement;
import com.slack.api.model.block.ContextActionsBlockElement;
import com.slack.api.model.block.LayoutBlock;
import com.slack.api.model.block.composition.TableCell;
import com.slack.api.model.block.composition.TextObject;
import com.slack.api.model.block.element.BlockElement;
import com.slack.api.model.block.element.RichTextElement;
Expand Down Expand Up @@ -80,6 +81,7 @@ public static void registerTypeAdapters(GsonBuilder builder, boolean failOnUnkno
.registerTypeAdapter(ContextActionsBlockElement.class, new GsonContextActionsBlockElementFactory(failOnUnknownProps))
.registerTypeAdapter(BlockElement.class, new GsonBlockElementFactory(failOnUnknownProps))
.registerTypeAdapter(RichTextElement.class, new GsonRichTextElementFactory(failOnUnknownProps))
.registerTypeAdapter(TableCell.class, new GsonTableCellFactory(failOnUnknownProps))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📢 note: Here we add custom deserialization to handle the various type possibilities of the table cell.

.registerTypeAdapter(FunctionExecutedEvent.InputValue.class, new GsonFunctionExecutedEventInputValueFactory())
.registerTypeAdapter(Attachment.VideoHtml.class, new GsonMessageAttachmentVideoHtmlFactory(failOnUnknownProps))
.registerTypeAdapter(MessageChangedEvent.PreviousMessage.class, new GsonMessageChangedEventPreviousMessageFactory(failOnUnknownProps))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ public static ContainerBlock container(ModelConfigurator<ContainerBlock.Containe
return configurator.configure(ContainerBlock.builder()).build();
}

// DataTableBlock

public static DataTableBlock dataTable(ModelConfigurator<DataTableBlock.DataTableBlockBuilder> configurator) {
return configurator.configure(DataTableBlock.builder()).build();
}

// TaskCardBlock

public static TaskCardBlock taskCard(ModelConfigurator<TaskCardBlock.TaskCardBlockBuilder> configurator) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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 rich tables that support pagination, sorting, filtering, and interactivity.
*
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/data-table-block">Data table block</a>
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DataTableBlock implements LayoutBlock {
public static final String TYPE = "data_table";
private final String type = TYPE;

/**
* An array consisting of table rows.
*/
@Builder.Default
private List<List<TableCell>> rows = new ArrayList<>();

/**
* A caption for the table; used as the value for the HTML caption element.
*/
private String caption;

/**
* Number of rows per page. Min {@code 1}, Max {@code 100}. Defaults to {@code 5} if omitted.
*/
private Integer pageSize;

/**
* The 0-based index of the column that uniquely identifies each row (the row header). This column
* is treated as the row's primary identifier for screen readers. Defaults to {@code 0} if omitted.
*/
private Integer rowHeaderColumnIndex;

private String blockId;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.slack.api.model.block;

import com.slack.api.model.block.composition.TableCell;
import com.slack.api.model.block.element.BlockElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -16,7 +17,7 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RichTextBlock implements LayoutBlock {
public class RichTextBlock implements LayoutBlock, TableCell {
public static final String TYPE = "rich_text";
private final String type = TYPE;
@Builder.Default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ public static MarkdownTextObject markdownText(String text) {
return MarkdownTextObject.builder().text(text).build();
}

// RawNumberObject

public static RawNumberObject rawNumber(ModelConfigurator<RawNumberObject.RawNumberObjectBuilder> configurator) {
return configurator.configure(RawNumberObject.builder()).build();
}

public static RawNumberObject rawNumber(Double value, String text) {
return RawNumberObject.builder().value(value).text(text).build();
}
Comment on lines +57 to +65

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️‍🗨️ note: These introduce helper functions to BlockComposition that aren't documented but IMHO are helpful for:

- RawTextObject.builder().text("Name").build()
+ BlockCompositions.rawText("Name"),


// RawTextObject

public static RawTextObject rawText(ModelConfigurator<RawTextObject.RawTextObjectBuilder> configurator) {
return configurator.configure(RawTextObject.builder()).build();
}

public static RawTextObject rawText(String text) {
return RawTextObject.builder().text(text).build();
}

// OptionGroupObject

public static OptionGroupObject optionGroup(ModelConfigurator<OptionGroupObject.OptionGroupObjectBuilder> configurator) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.slack.api.model.block.composition;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* Defines an object containing a numeric value.
*
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/table-block">Table block</a>
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/data-table-block">Data table block</a>
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RawNumberObject implements TableCell {
public static final String TYPE = "raw_number";
private final String type = TYPE;

/**
* The numeric value.
*/
private Double value;

/**
* The text used to display the value. The minimum length is 1 character.
*/
private String text;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.slack.api.model.block.composition;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* Defines an object containing some text.
*
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/table-block">Table block</a>
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/data-table-block">Data table block</a>
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RawTextObject implements TableCell {
public static final String TYPE = "raw_text";
private final String type = TYPE;

/**
* The text for the block. The minimum length is 1 character.
*/
private String text;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦠 note: I have some confidence this is a consistent shared type but it's not clear if it's meant to be stable because it's not in reference. Hoping to add this here and #1611 for shared implementation!

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.slack.api.model.block.composition;

/**
* A single cell within a table or data table row. A cell can be one of:
*
* <ul>
* <li>{@link RawTextObject} ({@code raw_text})</li>
* <li>{@link RawNumberObject} ({@code raw_number})</li>
* <li>{@link com.slack.api.model.block.RichTextBlock RichTextBlock} ({@code rich_text})</li>
* </ul>
*
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/table-block">Table block</a>
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/data-table-block">Data table block</a>
*/
public interface TableCell {

String getType();

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ private Class<? extends LayoutBlock> getLayoutClassInstance(String typeName) {
return ContextActionsBlock.class;
case ContextBlock.TYPE:
return ContextBlock.class;
case DataTableBlock.TYPE:
return DataTableBlock.class;
case DividerBlock.TYPE:
return DividerBlock.class;
case FileBlock.TYPE:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.slack.api.util.json;

import com.google.gson.*;
import com.slack.api.model.block.RichTextBlock;
import com.slack.api.model.block.composition.RawNumberObject;
import com.slack.api.model.block.composition.RawTextObject;
import com.slack.api.model.block.composition.TableCell;

import java.lang.reflect.Type;

/**
* Factory for deserializing the cells of a
* {@link com.slack.api.model.block.DataTableBlock data table block}.
*
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/data-table-block">Data table block</a>
*/
public class GsonTableCellFactory implements JsonDeserializer<TableCell>, JsonSerializer<TableCell> {

private final boolean failOnUnknownProperties;

public GsonTableCellFactory() {
this(false);
}

public GsonTableCellFactory(boolean failOnUnknownProperties) {
this.failOnUnknownProperties = failOnUnknownProperties;
}

@Override
public TableCell deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final JsonPrimitive prim = (JsonPrimitive) jsonObject.get("type");
final String typeName = prim.getAsString();
final Class<? extends TableCell> clazz = getTableCellClassInstance(typeName);
return context.deserialize(jsonObject, clazz);
}

@Override
public JsonElement serialize(TableCell src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src);
}

private Class<? extends TableCell> getTableCellClassInstance(String typeName) {
switch (typeName) {
case RawTextObject.TYPE:
return RawTextObject.class;
case RawNumberObject.TYPE:
return RawNumberObject.class;
case RichTextBlock.TYPE:
return RichTextBlock.class;
default:
throw new JsonParseException("Unknown table cell type: " + typeName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import com.slack.api.model.Message;
import com.slack.api.model.block.*;
import com.slack.api.model.block.composition.ConfirmationDialogObject;
import com.slack.api.model.block.composition.RawNumberObject;
import com.slack.api.model.block.composition.RawTextObject;
import com.slack.api.model.block.composition.TableCell;
import com.slack.api.model.block.element.*;
import com.slack.api.model.view.View;
import org.junit.Test;
Expand Down Expand Up @@ -1742,6 +1745,94 @@ public void parseVideoBlocks() {
assertEquals("https://www.youtube.com/embed/RRxQQxiM7AA?feature=oembed&autoplay=1", block.getVideoUrl());
}

@Test
public void parseDataTableBlock() {
// https://docs.slack.dev/reference/block-kit/blocks/data-table-block
String json = "{\n" +
" \"blocks\": [\n" +
" {\n" +
" \"type\": \"data_table\",\n" +
" \"block_id\": \"bid\",\n" +
" \"caption\": \"Quarterly results\",\n" +
" \"page_size\": 10,\n" +
" \"row_header_column_index\": 0,\n" +
" \"rows\": [\n" +
" [\n" +
" { \"type\": \"raw_text\", \"text\": \"Team\" },\n" +
" { \"type\": \"raw_text\", \"text\": \"Revenue\" }\n" +
" ],\n" +
" [\n" +
" {\n" +
" \"type\": \"rich_text\",\n" +
" \"elements\": [\n" +
" {\n" +
" \"type\": \"rich_text_section\",\n" +
" \"elements\": [ { \"type\": \"text\", \"text\": \"Platform\" } ]\n" +
" }\n" +
" ]\n" +
" },\n" +
" { \"type\": \"raw_number\", \"value\": 1234.5, \"text\": \"$1,234.50\" }\n" +
" ]\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}";
Message message = GsonFactory.createSnakeCase().fromJson(json, Message.class);
assertThat(message, is(notNullValue()));
assertThat(message.getBlocks().size(), is(1));

DataTableBlock block = (DataTableBlock) message.getBlocks().get(0);
assertThat(block.getType(), is("data_table"));
assertThat(block.getBlockId(), is("bid"));
assertThat(block.getCaption(), is("Quarterly results"));
assertThat(block.getPageSize(), is(10));
assertThat(block.getRowHeaderColumnIndex(), is(0));
assertThat(block.getRows().size(), is(2));

RawTextObject header = (RawTextObject) block.getRows().get(0).get(0);
assertThat(header.getType(), is("raw_text"));
assertThat(header.getText(), is("Team"));

TableCell richCell = block.getRows().get(1).get(0);
assertTrue(richCell instanceof RichTextBlock);
assertThat(richCell.getType(), is("rich_text"));

RawNumberObject numberCell = (RawNumberObject) block.getRows().get(1).get(1);
assertThat(numberCell.getType(), is("raw_number"));
assertThat(numberCell.getValue(), is(1234.5));
assertThat(numberCell.getText(), is("$1,234.50"));
}

@Test
public void buildDataTableBlock() {
DataTableBlock block = dataTable(t -> t
.blockId("bid")
.caption("Quarterly results")
.pageSize(10)
.rowHeaderColumnIndex(0)
.rows(Arrays.asList(
Arrays.asList(
RawTextObject.builder().text("Team").build(),
RawTextObject.builder().text("Revenue").build()
),
Arrays.asList(
RawTextObject.builder().text("Platform").build(),
RawNumberObject.builder().value(1234.5).text("$1,234.50").build()
)
)));
assertThat(block, is(notNullValue()));

Gson gson = GsonFactory.createSnakeCase();
String json = gson.toJson(block);
DataTableBlock restored = (DataTableBlock) gson.fromJson(json, LayoutBlock.class);
assertThat(restored.getType(), is("data_table"));
assertThat(restored.getCaption(), is("Quarterly results"));
assertThat(restored.getPageSize(), is(10));
assertThat(restored.getRows().size(), is(2));
RawNumberObject numberCell = (RawNumberObject) restored.getRows().get(1).get(1);
assertThat(numberCell.getValue(), is(1234.5));
}

@Test
public void parseAlertBlocks() {
// https://docs.slack.dev/reference/block-kit/blocks/alert-block
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.slack.api.model.block.ContextBlockElement;
import com.slack.api.model.block.ContextActionsBlockElement;
import com.slack.api.model.block.LayoutBlock;
import com.slack.api.model.block.composition.TableCell;
import com.slack.api.model.block.composition.TextObject;
import com.slack.api.model.block.element.BlockElement;
import com.slack.api.model.block.element.RichTextElement;
Expand Down Expand Up @@ -39,6 +40,7 @@ public static Gson createSnakeCase(boolean failOnUnknownProperties, boolean unkn
.registerTypeAdapter(ContextActionsBlockElement.class, new GsonContextActionsBlockElementFactory(failOnUnknownProperties))
.registerTypeAdapter(TextObject.class, new GsonTextObjectFactory(failOnUnknownProperties))
.registerTypeAdapter(RichTextElement.class, new GsonRichTextElementFactory(failOnUnknownProperties))
.registerTypeAdapter(TableCell.class, new GsonTableCellFactory(failOnUnknownProperties))
.registerTypeAdapter(FunctionExecutedEvent.InputValue.class,
new GsonFunctionExecutedEventInputValueFactory(failOnUnknownProperties))
.registerTypeAdapter(Attachment.VideoHtml.class,
Expand Down
Loading