diff --git a/block-kit/README.md b/block-kit/README.md index 3cd3768..c8eff89 100644 --- a/block-kit/README.md +++ b/block-kit/README.md @@ -9,6 +9,7 @@ Read the [docs](https://docs.slack.dev/block-kit/) to learn concepts behind thes ### Blocks - **[Actions](https://docs.slack.dev/reference/block-kit/blocks/actions-block)**: Holds multiple interactive elements. [Implementation](./src/main/java/blocks/Actions.java). +- **[Container](https://docs.slack.dev/reference/block-kit/blocks/container-block)**: A general-purpose wrapper for grouping child blocks together, with a configurable size. [Implementation](./src/main/java/blocks/Container.java). - **[Context](https://docs.slack.dev/reference/block-kit/blocks/context-block)**: Provides contextual info, which can include both images and text. [Implementation](./src/main/java/blocks/Context.java). - **[Divider](https://docs.slack.dev/reference/block-kit/blocks/divider-block)**: Visually separates pieces of info inside of a message. [Implementation](./src/main/java/blocks/Divider.java). - **[File](https://docs.slack.dev/reference/block-kit/blocks/file-block)**: Displays info about remote files. [Implementation](./src/main/java/blocks/File.java). diff --git a/block-kit/src/main/java/blocks/Container.java b/block-kit/src/main/java/blocks/Container.java new file mode 100644 index 0000000..7ed0d76 --- /dev/null +++ b/block-kit/src/main/java/blocks/Container.java @@ -0,0 +1,45 @@ +package blocks; + +import com.slack.api.model.block.Blocks; +import com.slack.api.model.block.ContainerBlock; +import com.slack.api.model.block.composition.BlockCompositions; +import com.slack.api.model.block.element.BlockElements; +import java.util.List; + +/** + * A general-purpose wrapper for grouping child blocks together, with a configurable size. + * {@link https://docs.slack.dev/reference/block-kit/blocks/container-block/} + */ +public class Container { + /** + * A collapsible container with sections, a divider, context, and actions. + */ + public static ContainerBlock example01() { + ContainerBlock block = Blocks.container(c -> c.blockId("bkb_container_bulk_update") + .title(BlockCompositions.plainText("Bulk update: 2 records selected")) + .subtitle(BlockCompositions.plainText("Review changes before confirming")) + .isCollapsible(true) + .childBlocks(Blocks.asBlocks( + Blocks.section(s -> s.blockId("record-row-1") + .text(BlockCompositions.markdownText( + "*DCW-1024*\nStatus: Open → Closed\nAssignee: @princessdonut → @carl"))), + Blocks.divider(d -> d.blockId("bulk-div-1")), + Blocks.section(s -> s.blockId("record-row-2") + .text(BlockCompositions.markdownText( + "*DCW-1025*\nStatus: In Progress → Closed\nAssignee: @mordecai → @carl"))), + Blocks.divider(d -> d.blockId("bulk-div-2")), + Blocks.context(ctx -> ctx.blockId("bulk-status-bar") + .elements(List.of(BlockCompositions.markdownText( + ":white_check_mark: 2 records will be updated • Status → Closed • Assignee → @carl")))), + Blocks.actions(a -> a.blockId("bulk-actions") + .elements(List.of( + BlockElements.button(b -> b.text( + BlockCompositions.plainText(pt -> pt.text("Confirm All").emoji(true))) + .style("primary") + .actionId("bulk_confirm")), + BlockElements.button(b -> b.text( + BlockCompositions.plainText(pt -> pt.text("Cancel").emoji(true))) + .actionId("bulk_cancel")))))))); + return block; + } +} diff --git a/block-kit/src/test/java/blocks/ContainerTest.java b/block-kit/src/test/java/blocks/ContainerTest.java new file mode 100644 index 0000000..ce9bd9d --- /dev/null +++ b/block-kit/src/test/java/blocks/ContainerTest.java @@ -0,0 +1,93 @@ +package blocks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.google.gson.JsonParser; +import com.slack.api.model.block.ContainerBlock; +import com.slack.api.util.json.GsonFactory; +import org.junit.jupiter.api.Test; + +public class ContainerTest { + @Test + public void testExample01() { + ContainerBlock block = Container.example01(); + String actual = GsonFactory.createSnakeCase().toJson(block); + String expected = """ + { + "type": "container", + "block_id": "bkb_container_bulk_update", + "title": { + "type": "plain_text", + "text": "Bulk update: 2 records selected" + }, + "subtitle": { + "type": "plain_text", + "text": "Review changes before confirming" + }, + "is_collapsible": true, + "child_blocks": [ + { + "type": "section", + "block_id": "record-row-1", + "text": { + "type": "mrkdwn", + "text": "*DCW-1024*\\nStatus: Open → Closed\\nAssignee: @princessdonut → @carl" + } + }, + { + "type": "divider", + "block_id": "bulk-div-1" + }, + { + "type": "section", + "block_id": "record-row-2", + "text": { + "type": "mrkdwn", + "text": "*DCW-1025*\\nStatus: In Progress → Closed\\nAssignee: @mordecai → @carl" + } + }, + { + "type": "divider", + "block_id": "bulk-div-2" + }, + { + "type": "context", + "block_id": "bulk-status-bar", + "elements": [ + { + "type": "mrkdwn", + "text": ":white_check_mark: 2 records will be updated • Status → Closed • Assignee → @carl" + } + ] + }, + { + "type": "actions", + "block_id": "bulk-actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Confirm All", + "emoji": true + }, + "style": "primary", + "action_id": "bulk_confirm" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Cancel", + "emoji": true + }, + "action_id": "bulk_cancel" + } + ] + } + ] + } + """; + assertEquals(JsonParser.parseString(expected), JsonParser.parseString(actual)); + } +}