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
@@ -0,0 +1,86 @@
package com.slack.api.model.kotlin_extension.block

import com.slack.api.model.block.ContainerBlock
import com.slack.api.model.block.LayoutBlock
import com.slack.api.model.block.RichTextBlock
import com.slack.api.model.block.composition.PlainTextObject
import com.slack.api.model.block.composition.TextObject
import com.slack.api.model.block.element.ImageElement
import com.slack.api.model.kotlin_extension.block.container.MultiLayoutBlockContainer

@BlockLayoutBuilder
class ContainerBlockBuilder : Builder<ContainerBlock> {
private var blockId: String? = null
private var title: TextObject? = null
private var richTextTitle: RichTextBlock? = null
private var subtitle: TextObject? = null
private var childBlocks: List<LayoutBlock>? = null
private var width: String? = null
private var icon: ImageElement? = null
private var isCollapsible: Boolean? = null
private var defaultCollapsed: Boolean? = null
private var hasHeaderDivider: Boolean? = null

fun blockId(id: String) {
blockId = id
}

fun title(text: String, emoji: Boolean? = null) {
title = PlainTextObject(text, emoji)
}

fun title(textObject: TextObject) {
title = textObject
}

fun richTextTitle(richText: RichTextBlock) {
richTextTitle = richText
}

fun subtitle(text: String, emoji: Boolean? = null) {
subtitle = PlainTextObject(text, emoji)
}

fun subtitle(textObject: TextObject) {
subtitle = textObject
}

fun childBlocks(builder: MultiLayoutBlockContainer.() -> Unit) {
childBlocks = MultiLayoutBlockContainer().apply(builder).underlying
}

fun width(width: String) {
this.width = width
}

fun icon(imageUrl: String, altText: String) {
icon = ImageElement.builder().imageUrl(imageUrl).altText(altText).build()
}

fun isCollapsible(collapsible: Boolean) {
isCollapsible = collapsible
}

fun defaultCollapsed(collapsed: Boolean) {
defaultCollapsed = collapsed
}

fun hasHeaderDivider(divider: Boolean) {
hasHeaderDivider = divider
}

override fun build(): ContainerBlock {
return ContainerBlock.builder()
.blockId(blockId)
.title(title)
.richTextTitle(richTextTitle)
.subtitle(subtitle)
.childBlocks(childBlocks ?: ArrayList())
.width(width)
.icon(icon)
.isCollapsible(isCollapsible)
.defaultCollapsed(defaultCollapsed)
.hasHeaderDivider(hasHeaderDivider)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,8 @@ class MultiLayoutBlockContainer : LayoutBlockDsl {
override fun shareShortcut(builder: ShareShortcutBlockBuilder.() -> Unit) {
underlying += ShareShortcutBlockBuilder().apply(builder).build()
}

override fun container(builder: ContainerBlockBuilder.() -> Unit) {
underlying += ContainerBlockBuilder().apply(builder).build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,11 @@ interface LayoutBlockDsl {
* @see <a href="https://tools.slack.dev/deno-slack-sdk/">Next generation platform</a>
*/
fun shareShortcut(builder: ShareShortcutBlockBuilder.() -> Unit)

/**
* A container block groups related blocks together into a visually distinct section.
*
* @see <a href="https://docs.slack.dev/reference/block-kit/blocks/container-block">Container documentation</a>
*/
fun container(builder: ContainerBlockBuilder.() -> Unit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,14 @@
return configurator.configure(CarouselBlock.builder()).build();
}

// ContainerBlock

public static ContainerBlock container(ModelConfigurator<ContainerBlock.ContainerBlockBuilder> configurator) {
return configurator.configure(ContainerBlock.builder()).build();
// TaskCardBlock

public static TaskCardBlock taskCard(ModelConfigurator<TaskCardBlock.TaskCardBlockBuilder> configurator) {

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

';' expected

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

';' expected

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

illegal start of expression

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

illegal start of expression

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

';' expected

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

';' expected

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

illegal start of expression

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

illegal start of expression

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (17)

illegal start of expression

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (17)

illegal start of expression

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (14)

illegal start of expression

Check failure on line 156 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (14)

illegal start of expression
return configurator.configure(TaskCardBlock.builder()).build();
}

}

Check failure on line 160 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

reached end of file while parsing

Check failure on line 160 in slack-api-model/src/main/java/com/slack/api/model/block/Blocks.java

View workflow job for this annotation

GitHub Actions / build (8)

reached end of file while parsing
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.slack.api.model.block;

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

import java.util.ArrayList;
import java.util.List;

/**
* https://docs.slack.dev/reference/block-kit/blocks/container-block
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ContainerBlock implements LayoutBlock {
public static final String TYPE = "container";
private final String type = TYPE;

private TextObject title;

private RichTextBlock richTextTitle;

private TextObject subtitle;

@Builder.Default
private List<LayoutBlock> childBlocks = new ArrayList<>();

private String width;

private ImageElement icon;

private Boolean isCollapsible;

private Boolean defaultCollapsed;

private Boolean hasHeaderDivider;

private String blockId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2169,6 +2169,154 @@ public void parseCarouselBlock() {
assertThat(reparsed.getElements().get(0).getType(), is("card"));
}

@Test
public void parseContainerBlock() {
// https://docs.slack.dev/reference/block-kit/blocks/container-block
String json = "{\"blocks\":[\n" +
" {\n" +
" \"type\": \"container\",\n" +
" \"block_id\": \"container1\",\n" +
" \"title\": {\n" +
" \"type\": \"plain_text\",\n" +
" \"text\": \"Container Title\"\n" +
" },\n" +
" \"subtitle\": {\n" +
" \"type\": \"mrkdwn\",\n" +
" \"text\": \"*Bold* subtitle\"\n" +
" },\n" +
" \"width\": \"wide\",\n" +
" \"is_collapsible\": true,\n" +
" \"default_collapsed\": false,\n" +
" \"icon\": {\n" +
" \"type\": \"image\",\n" +
" \"image_url\": \"https://example.com/icon.png\",\n" +
" \"alt_text\": \"icon\"\n" +
" },\n" +
" \"child_blocks\": [\n" +
" {\n" +
" \"type\": \"section\",\n" +
" \"text\": {\n" +
" \"type\": \"mrkdwn\",\n" +
" \"text\": \"Section inside container.\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"type\": \"divider\"\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));

ContainerBlock container = (ContainerBlock) message.getBlocks().get(0);
assertThat(container.getType(), is("container"));
assertThat(container.getBlockId(), is("container1"));
assertThat(container.getTitle().getType(), is("plain_text"));
assertThat(container.getTitle().getText(), is("Container Title"));
assertThat(container.getSubtitle().getType(), is("mrkdwn"));
assertThat(container.getWidth(), is("wide"));
assertThat(container.getIsCollapsible(), is(true));
assertThat(container.getDefaultCollapsed(), is(false));
assertThat(container.getIcon().getImageUrl(), is("https://example.com/icon.png"));
assertThat(container.getChildBlocks().size(), is(2));
assertThat(container.getChildBlocks().get(0), is(instanceOf(SectionBlock.class)));
assertThat(container.getChildBlocks().get(1), is(instanceOf(DividerBlock.class)));

// Round-trip
Message roundTrip = gson.fromJson(gson.toJson(message), Message.class);
ContainerBlock reparsed = (ContainerBlock) roundTrip.getBlocks().get(0);
assertThat(reparsed.getBlockId(), is("container1"));
assertThat(reparsed.getChildBlocks().size(), is(2));
}

@Test
public void parseContainerBlockWithRichTextTitle() {
String json = "{\"blocks\":[\n" +
" {\n" +
" \"type\": \"container\",\n" +
" \"rich_text_title\": {\n" +
" \"type\": \"rich_text\",\n" +
" \"elements\": [\n" +
" {\n" +
" \"type\": \"rich_text_section\",\n" +
" \"elements\": [\n" +
" {\n" +
" \"type\": \"text\",\n" +
" \"text\": \"Rich Title\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"child_blocks\": [\n" +
" {\n" +
" \"type\": \"divider\"\n" +
" }\n" +
" ]\n" +
" }\n" +
"]}";
Gson gson = GsonFactory.createSnakeCase();
Message message = gson.fromJson(json, Message.class);
assertThat(message, is(notNullValue()));
ContainerBlock container = (ContainerBlock) message.getBlocks().get(0);
assertThat(container.getRichTextTitle(), is(notNullValue()));
assertThat(container.getRichTextTitle().getType(), is("rich_text"));
assertThat(container.getRichTextTitle().getElements().size(), is(1));
assertThat(container.getTitle(), is(nullValue()));
}

@Test
public void parseContainerBlockWithHeaderDivider() {
String json = "{\"blocks\":[\n" +
" {\n" +
" \"type\": \"container\",\n" +
" \"title\": {\n" +
" \"type\": \"plain_text\",\n" +
" \"text\": \"With divider\"\n" +
" },\n" +
" \"has_header_divider\": true,\n" +
" \"child_blocks\": [\n" +
" {\n" +
" \"type\": \"divider\"\n" +
" }\n" +
" ]\n" +
" }\n" +
"]}";
Gson gson = GsonFactory.createSnakeCase();
Message message = gson.fromJson(json, Message.class);
ContainerBlock container = (ContainerBlock) message.getBlocks().get(0);
assertThat(container.getHasHeaderDivider(), is(true));
assertThat(container.getIsCollapsible(), is(nullValue()));
}

@Test
public void buildContainerBlock() {
ContainerBlock container = container(c -> c
.blockId("container1")
.title(com.slack.api.model.block.composition.PlainTextObject.builder().text("Title").build())
.width("wide")
.isCollapsible(true)
.childBlocks(Arrays.asList(
divider()
))
);
assertThat(container.getType(), is("container"));
assertThat(container.getBlockId(), is("container1"));

Gson gson = GsonFactory.createSnakeCase();
Message message = new Message();
message.setBlocks(asBlocks(container));
Message reparsed = gson.fromJson(gson.toJson(message), Message.class);
ContainerBlock parsedContainer = (ContainerBlock) reparsed.getBlocks().get(0);
assertThat(parsedContainer.getBlockId(), is("container1"));
assertThat(parsedContainer.getWidth(), is("wide"));
assertThat(parsedContainer.getIsCollapsible(), is(true));
assertThat(parsedContainer.getChildBlocks().size(), is(1));
}

@Test
public void buildCarouselBlock() {
CarouselBlock carousel = carousel(c -> c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ public void testCarousel() {
))), is(notNullValue()));
}

@Test
public void testContainer() {
assertThat(container(c -> c.blockId("container-1")
.title(com.slack.api.model.block.composition.PlainTextObject.builder().text("Title").build())
.childBlocks(Arrays.asList(divider()))
), is(notNullValue()));
}

@Test
public void testImage() {
assertThat(Blocks.image(i -> i.blockId("block-id").imageUrl("https://www.example.com/")), is(notNullValue()));
Expand Down
Loading