Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #477 by adding default_to_current_conversation to conv elements #478

Merged
merged 4 commits into from
Jun 11, 2020
Merged
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,50 @@
package samples;

import com.slack.api.bolt.App;
import com.slack.api.bolt.AppConfig;
import com.slack.api.methods.response.views.ViewsOpenResponse;
import com.slack.api.model.view.View;
import util.ResourceLoader;
import util.TestSlackAppServer;

import java.net.URLDecoder;

public class ViewDefaultToCurrentConversationSample {

public static void main(String[] args) throws Exception {
AppConfig config = ResourceLoader.loadAppConfig("appConfig_defaultToCurrentConversation.json");
App app = new App(config);
app.use((req, resp, chain) -> {
String body = req.getRequestBodyAsString();
String payload = body.startsWith("payload=") ? URLDecoder.decode(body.split("payload=")[1], "UTF-8") : body;
req.getContext().logger.info(payload);
return chain.next(req);
});

// src/test/resources
app.command("/view", (req, ctx) -> {
String view = ResourceLoader.load("views/view5.json");
ViewsOpenResponse apiResponse = ctx.client().viewsOpen(r ->
r.triggerId(ctx.getTriggerId()).viewAsString(view));
if (apiResponse.isOk()) {
return ctx.ack();
} else {
return ctx.ackWithJson(apiResponse);
}
});

app.viewSubmission("view-callback-id", (req, ctx) -> {
View view = req.getPayload().getView();
ctx.logger.info("state - {}, private_metadata - {}", view.getState(), view.getPrivateMetadata());
String conversationId = view.getState().getValues().get("b1").get("a").getSelectedConversation();
ctx.say(r -> r.channel(conversationId).text("Thank you for submitting the data!"));
return ctx.ack(); // just close the view
});

app.viewClosed("view-callback-id", (req, ctx) -> ctx.ack());

TestSlackAppServer server = new TestSlackAppServer(app);
server.start();
}

}
48 changes: 48 additions & 0 deletions bolt-servlet/src/test/resources/views/view5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"callback_id": "view-callback-id",
"type": "modal",
"notify_on_close": true,
"title": {
"type": "plain_text",
"text": "My App",
"emoji": true
},
"submit": {
"type": "plain_text",
"text": "Submit",
"emoji": true
},
"close": {
"type": "plain_text",
"text": "Cancel",
"emoji": true
},
"blocks": [
{
"type": "input",
"block_id": "b1",
"element": {
"type": "conversations_select",
"action_id": "a",
"default_to_current_conversation": true
},
"label": {
"type": "plain_text",
"text": "Single"
}
},
{
"type": "input",
"block_id": "b2",
"element": {
"type": "multi_conversations_select",
"action_id": "a",
"default_to_current_conversation": true
},
"label": {
"type": "plain_text",
"text": "Multi"
}
}
]
}
26 changes: 25 additions & 1 deletion docs/guides/ja/modals.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,34 @@ ctx.ackWithPush(newViewInStack)

#### モーダル送信後にメッセージを投稿

`view_submission` のペイロードはデフォルトでは `response_url` を含んでいません。しかし、モーダルがユーザーにメッセージを投稿するためのチャンネルを入力するよう求める `input` タイプのブロックを含む場合、ペイロード内の `response_urls` として URL を受け取ることができます。
`view_submission` のペイロードはデフォルトでは `response_url` を含んでいません。しかし、モーダルがユーザーにメッセージを投稿するためのチャンネルを入力するよう求める `input` タイプのブロックを含む場合、ペイロード内の `response_urls` (Java では `List<ResponseUrl> responseUrls` となります) として URL を受け取ることができます。

これを有効にするためには [`channels_select`](https://api.slack.com/reference/block-kit/block-elements#channel_select) もしくは [`conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_select) のタイプのブロックエレメントを配置し、さらにその属性として `"response_url_enabled": true` を追加してください。より詳細な情報は [API ドキュメント(英語)](https://api.slack.com/surfaces/modals/using#modal_response_url)を参照してください。

また、ユーザーがモーダルを開いたときに見ていたチャンネルや DM を `initial_conversation(s)` として自動的に反映したい場合は [`conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_select) / [`multi_conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_multi_select) エレメントの `default_to_current_conversation` を有効にしてください。

```java
import static com.slack.api.model.block.Blocks.*;
import static com.slack.api.model.block.composition.BlockCompositions.*;
import static com.slack.api.model.block.element.BlockElements.*;
import static com.slack.api.model.view.Views.*;

View modalView = view(v -> v
.type("modal")
.callbackId("request-modal")
.submit(viewSubmit(vs -> vs.type("plain_text").text("Start")))
.blocks(asBlocks(
section(s -> s
.text(plainText("The channel we'll post the result"))
.accessory(conversationsSelect(conv -> conv
.actionId("notification_conv_id")
.responseUrlEnabled(true)
.defaultToCurrentConversation(true)
))
)
)));
```

### `"view_closed"` リクエスト (`notify_on_close` が `true` のときのみ)

Bolt は Slack アプリに必要な共通処理の多くを巻き取ります。それを除いて、あなたのアプリがやらなければならない手順は以下の通りです。
Expand Down
26 changes: 25 additions & 1 deletion docs/guides/modals.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,34 @@ ctx.ack { it.responseAction("push").view(newViewInStack) }

#### Publishing Messages After Modal Submissions

`view_submission` payloads don't have `response_url` by default. However, if you have an `input` block asking users a channel to post a message, payloads may provide `response_urls`.
`view_submission` payloads don't have `response_url` by default. However, if you have an `input` block asking users a channel to post a message, payloads may provide `response_urls` (`List<ResponseUrl> responseUrls` in Java).

To enable this, set the block element type as either [`channels_select`](https://api.slack.com/reference/block-kit/block-elements#channel_select) or [`conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_select) and add `"response_url_enabled": true`. Refer to [the API document](https://api.slack.com/surfaces/modals/using#modal_response_url) for details.

Also, if you want to automatically set the channel a user is viewing when opening a modal to`initial_conversation(s)`, turn `default_to_current_conversation` on in [`conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_select) / [`multi_conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_multi_select) elements.

```java
import static com.slack.api.model.block.Blocks.*;
import static com.slack.api.model.block.composition.BlockCompositions.*;
import static com.slack.api.model.block.element.BlockElements.*;
import static com.slack.api.model.view.Views.*;

View modalView = view(v -> v
.type("modal")
.callbackId("request-modal")
.submit(viewSubmit(vs -> vs.type("plain_text").text("Start")))
.blocks(asBlocks(
section(s -> s
.text(plainText("The channel we'll post the result"))
.accessory(conversationsSelect(conv -> conv
.actionId("notification_conv_id")
.responseUrlEnabled(true)
.defaultToCurrentConversation(true)
))
)
)));
```

### `"view_closed"` requests (only when `notify_on_close` is `true`)

Bolt does many of the commonly required tasks for you. The steps you need to handle would be:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
},
"action_id": "",
"initial_conversation": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down Expand Up @@ -191,6 +192,7 @@
"emoji": false
},
"action_id": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
},
"action_id": "",
"initial_conversation": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down Expand Up @@ -167,6 +168,7 @@
"emoji": false
},
"action_id": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down
1 change: 1 addition & 0 deletions json-logs/samples/rtm/MessageEvent.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
},
"action_id": "",
"initial_conversation": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down
1 change: 1 addition & 0 deletions json-logs/samples/rtm/PinAddedEvent.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
},
"action_id": "",
"initial_conversation": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down
1 change: 1 addition & 0 deletions json-logs/samples/rtm/PinRemovedEvent.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
},
"action_id": "",
"initial_conversation": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down
1 change: 1 addition & 0 deletions json-logs/samples/rtm/StarAddedEvent.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
},
"action_id": "",
"initial_conversation": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down
1 change: 1 addition & 0 deletions json-logs/samples/rtm/StarRemovedEvent.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
},
"action_id": "",
"initial_conversation": "",
"default_to_current_conversation": false,
"confirm": {
"title": {
"type": "plain_text",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public class ConversationsSelectElement extends BlockElement {
*/
private String initialConversation;

/**
* Pre-populates the select menu with the conversation that the user was viewing when they opened the modal,
* if available. If initial_conversation is also supplied, it will be ignored. Default is false.
*/
private Boolean defaultToCurrentConversation;

/**
* A confirm object that defines an optional confirmation dialog that appears after a menu item is selected.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public class MultiConversationsSelectElement extends BlockElement {
*/
private List<String> initialConversations;

/**
* Pre-populates the select menu with the conversation that the user was viewing when they opened the modal,
* if available. If initial_conversation is also supplied, it will be ignored. Default is false.
*/
private Boolean defaultToCurrentConversation;

/**
* A confirm object that defines an optional confirmation dialog that appears
* before the multi-select choices are submitted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.slack.api.model.block.element.ChannelsSelectElement;
import com.slack.api.model.block.element.ConversationsSelectElement;
import com.slack.api.model.block.element.MultiConversationsSelectElement;
import com.slack.api.model.view.View;
import org.junit.Test;
import test_locally.unit.GsonFactory;

Expand All @@ -16,6 +17,8 @@
import static com.slack.api.model.block.composition.BlockCompositions.asSectionFields;
import static com.slack.api.model.block.composition.BlockCompositions.plainText;
import static com.slack.api.model.block.element.BlockElements.*;
import static com.slack.api.model.view.Views.view;
import static com.slack.api.model.view.Views.viewSubmit;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
Expand Down Expand Up @@ -322,4 +325,73 @@ public void testResponseUrlEnabled_channels() {
}
}

@Test
public void defaultToCurrentConversation() {
String json = "{\n" +
" \"blocks\": [\n" +
" {\n" +
" \"type\": \"section\",\n" +
" \"text\": {\n" +
" \"type\": \"mrkdwn\",\n" +
" \"text\": \"Test block with multi conversations select\"\n" +
" },\n" +
" \"accessory\": {\n" +
" \"type\": \"multi_conversations_select\",\n" +
" \"placeholder\": {\n" +
" \"type\": \"plain_text\",\n" +
" \"text\": \"Select conversations\",\n" +
" \"emoji\": true\n" +
" },\n" +
" \"default_to_current_conversation\": true\n" +
" }\n" +
" },\n" +
" {\n" +
" \"type\": \"section\",\n" +
" \"text\": {\n" +
" \"type\": \"mrkdwn\",\n" +
" \"text\": \"Test block with multi conversations select\"\n" +
" },\n" +
" \"accessory\": {\n" +
" \"type\": \"conversations_select\",\n" +
" \"placeholder\": {\n" +
" \"type\": \"plain_text\",\n" +
" \"text\": \"Select conversations\",\n" +
" \"emoji\": true\n" +
" },\n" +
" \"default_to_current_conversation\": true\n" +
" }\n" +
" }\n" +
" ]\n" +
"}";
Message message = GsonFactory.createSnakeCase().fromJson(json, Message.class);
assertNotNull(message);
assertEquals(2, message.getBlocks().size());
SectionBlock section1 = (SectionBlock) message.getBlocks().get(0);
MultiConversationsSelectElement elem1 = (MultiConversationsSelectElement) (section1).getAccessory();
assertTrue(elem1.getDefaultToCurrentConversation());
SectionBlock section2 = (SectionBlock) message.getBlocks().get(1);
ConversationsSelectElement elem2 = (ConversationsSelectElement) (section2).getAccessory();
assertTrue(elem2.getDefaultToCurrentConversation());
}

@Test
public void codeInDocs() {
View modalView = view(v -> v
.type("modal")
.callbackId("view-id")
.submit(viewSubmit(vs -> vs.type("plain_text").text("Submit")))
.blocks(asBlocks(
section(s -> s
.text(plainText("Conversation to post the result"))
.accessory(conversationsSelect(conv -> conv
.actionId("a")
.defaultToCurrentConversation(true)
.responseUrlEnabled(true))
)
)
)));
assertNotNull(modalView);
}


}