Skip to content
Draft
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 @@ -129,4 +129,10 @@ public static ShareShortcutBlock shareShortcut() {
return ShareShortcutBlock.builder().build();
}

// CardBlock

public static CardBlock card(ModelConfigurator<CardBlock.CardBlockBuilder> configurator) {
return configurator.configure(CardBlock.builder()).build();
}

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

import com.slack.api.model.block.composition.TextObject;
import com.slack.api.model.block.element.BlockElement;
import com.slack.api.model.block.element.ImageElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
* A card is a layout block used to display a compact, structured summary of content. It can be used on
* its own or grouped together inside a {@link CarouselBlock carousel}. At least one of {@code heroImage},
* {@code title}, {@code actions}, or {@code body} must be provided.
*
* https://docs.slack.dev/reference/block-kit/blocks/card-block
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CardBlock implements LayoutBlock {
public static final String TYPE = "card";
private final String type = TYPE;

/**
* A top banner image for the card in the form of an image element.
* The image URL may be up to 3000 characters and the alt text up to 2000 characters.
*/
private ImageElement heroImage;

/**
* A small icon displayed next to the title and subtitle in the form of an image element.
* The image URL may be up to 3000 characters and the alt text up to 2000 characters.
* Mutually exclusive with {@code slackIcon}.
*/
private ImageElement icon;

/**
* The name of a built-in Slack icon displayed next to the title and subtitle.
* Mutually exclusive with {@code icon}.
*/
private String slackIcon;

/**
* The title of the card. Maximum length for the text in this field is 150 characters.
*/
private TextObject title;

/**
* The subtitle of the card. Maximum length for the text in this field is 150 characters.
*/
private TextObject subtitle;

/**
* The body text of the card. Maximum length for the text in this field is 200 characters.
*/
private TextObject body;

/**
* Secondary text displayed beneath the body of the card. Maximum length for the text in this field
* is 200 characters.
*/
private TextObject subtext;

/**
* Interactive elements (such as buttons) displayed at the bottom of the card. Up to 3 buttons.
*/
private List<BlockElement> actions;

private String blockId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ private Class<? extends LayoutBlock> getLayoutClassInstance(String typeName) {
return RichTextBlock.class;
case ShareShortcutBlock.TYPE:
return ShareShortcutBlock.class;
case CardBlock.TYPE:
return CardBlock.class;
default:
if (failOnUnknownProperties) {
throw new JsonParseException("Unsupported layout block type: " + typeName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1940,4 +1940,98 @@ public void parseRichTextElements() {
RichTextSectionElement section = (RichTextSectionElement) ((RichTextBlock) message.getBlocks().get(0)).getElements().get(0);
assertThat(section.getElements().size(), is(10));
}

@Test
public void parseCardBlock() {
// https://docs.slack.dev/reference/block-kit/blocks/card-block
String json = "{\"blocks\":[\n" +
" {\n" +
" \"type\": \"card\",\n" +
" \"block_id\": \"card1\",\n" +
" \"icon\": {\n" +
" \"type\": \"image\",\n" +
" \"image_url\": \"https://picsum.photos/36/36\",\n" +
" \"alt_text\": \"Icon\"\n" +
" },\n" +
" \"hero_image\": {\n" +
" \"type\": \"image\",\n" +
" \"image_url\": \"https://picsum.photos/400/300\",\n" +
" \"alt_text\": \"Sample hero image\"\n" +
" },\n" +
" \"title\": {\n" +
" \"type\": \"mrkdwn\",\n" +
" \"text\": \"Lumon Industries\"\n" +
" },\n" +
" \"subtitle\": {\n" +
" \"type\": \"mrkdwn\",\n" +
" \"text\": \"Committed to work-life balance\"\n" +
" },\n" +
" \"body\": {\n" +
" \"type\": \"mrkdwn\",\n" +
" \"text\": \"Please enjoy each card equally.\"\n" +
" },\n" +
" \"actions\": [\n" +
" {\n" +
" \"type\": \"button\",\n" +
" \"text\": {\n" +
" \"type\": \"plain_text\",\n" +
" \"text\": \"Action Button\"\n" +
" },\n" +
" \"action_id\": \"button_action\"\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));

CardBlock card = (CardBlock) message.getBlocks().get(0);
assertThat(card.getType(), is("card"));
assertThat(card.getBlockId(), is("card1"));
assertThat(card.getIcon().getImageUrl(), is("https://picsum.photos/36/36"));
assertThat(card.getIcon().getAltText(), is("Icon"));
assertThat(card.getHeroImage().getImageUrl(), is("https://picsum.photos/400/300"));
assertThat(card.getHeroImage().getAltText(), is("Sample hero image"));
assertThat(card.getTitle().getType(), is("mrkdwn"));
assertThat(((com.slack.api.model.block.composition.MarkdownTextObject) card.getTitle()).getText(), is("Lumon Industries"));
assertThat(((com.slack.api.model.block.composition.MarkdownTextObject) card.getSubtitle()).getText(), is("Committed to work-life balance"));
assertThat(((com.slack.api.model.block.composition.MarkdownTextObject) card.getBody()).getText(), is("Please enjoy each card equally."));
assertThat(card.getActions().size(), is(1));
ButtonElement button = (ButtonElement) card.getActions().get(0);
assertThat(button.getActionId(), is("button_action"));
assertThat(button.getText().getText(), is("Action Button"));

// Round-trip back to JSON and ensure the card survives serialization.
Message roundTrip = gson.fromJson(gson.toJson(message), Message.class);
CardBlock reparsed = (CardBlock) roundTrip.getBlocks().get(0);
assertThat(reparsed.getBlockId(), is("card1"));
assertThat(reparsed.getActions().size(), is(1));
assertThat(((ButtonElement) reparsed.getActions().get(0)).getActionId(), is("button_action"));
}

@Test
public void buildCardBlock() {
CardBlock card = card(c -> c
.blockId("card2")
.title(com.slack.api.model.block.composition.MarkdownTextObject.builder().text("Title").build())
.body(com.slack.api.model.block.composition.PlainTextObject.builder().text("Body").build())
.actions(Arrays.asList(
ButtonElement.builder()
.actionId("a")
.text(plainText("Click"))
.build()
))
);
assertThat(card.getType(), is("card"));
assertThat(card.getBlockId(), is("card2"));
Gson gson = GsonFactory.createSnakeCase();
Message message = new Message();
message.setBlocks(asBlocks(card));
Message reparsed = gson.fromJson(gson.toJson(message), Message.class);
CardBlock parsedCard = (CardBlock) reparsed.getBlocks().get(0);
assertThat(parsedCard.getBlockId(), is("card2"));
assertThat(parsedCard.getActions().size(), is(1));
}
}