Skip to content
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
Expand Up @@ -14,14 +14,14 @@ public static void main(String[] args) throws Exception {
AppConfig config = ResourceLoader.loadAppConfig();
App app = new App(config);

// https://github.com/slackapi/java-slack-sdk/blob/master/bolt/src/test/resources/attachments/message1.json
// src/test/resources/attachments/message1.json
String firstMessage = ResourceLoader.load("attachments/message1.json");
app.command("/view", (req, ctx) -> {
ctx.respond(JsonOps.fromJson(firstMessage, SlashCommandResponse.class));
return ctx.ack();
});

// https://github.com/slackapi/java-slack-sdk/blob/master/bolt/src/test/resources/action_response/message1.json
// src/test/resources/action_response/message1.json
String secondMessage = ResourceLoader.load("action_response/message1.json");
app.attachmentAction("wopr_game", (req, ctx) -> {
ctx.logger.info("attachment action - {}", req.getPayload());
Expand Down
2 changes: 1 addition & 1 deletion bolt-servlet/src/test/java/samples/DialogSample.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static void main(String[] args) throws Exception {
App app = new App(config);

app.command("/dialog", (req, ctx) -> {
// https://github.com/slackapi/java-slack-sdk/blob/master/bolt/src/test/resources/dialogs/dialog.json
// src/test/resources/dialogs/dialog.json
String dialog = ResourceLoader.load("dialogs/dialog.json");
DialogOpenResponse apiResponse = ctx.client().dialogOpen(r -> r
.triggerId(req.getPayload().getTriggerId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static void main(String[] args) throws Exception {

// https://api.slack.com/apps/{your apiAppId}/interactive-messages
app.messageShortcut("open-view", (req, ctx) -> {
// https://github.com/slackapi/java-slack-sdk/blob/master/bolt/src/test/resources/views/view3.json
// src/test/resources/views/view3.json
String view = ResourceLoader.load("views/view3.json");

ChatGetPermalinkResponse permalinkResponse = ctx.client().chatGetPermalink(r -> r
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package samples;

import com.slack.api.bolt.App;
import com.slack.api.bolt.AppConfig;
import com.slack.api.bolt.response.Responder;
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 ViewResponseUrlEnabledSample {

public static void main(String[] args) throws Exception {
AppConfig config = ResourceLoader.loadAppConfig("appConfig_responseUrlEnabled.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
String view1 = ResourceLoader.load("views/view4.json");
app.command("/view", (req, ctx) -> {
ViewsOpenResponse apiResponse = ctx.client().viewsOpen(r -> r
.triggerId(ctx.getTriggerId())
.viewAsString(view1)
);
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());

ctx.respond("[ephemeral] Thank you for submitting the data!");
ctx.respond(r -> r.responseType("in_channel").text("[in_channel] Thank you for submitting the data!"));

ctx.getResponder("b1", "a").send("This message was sent via response_url given by channels_select block element (block_id: b1, action_id: a)");

String responseUrl = ctx.getResponseUrls().get(0).getResponseUrl();
new Responder(ctx.getSlack(), responseUrl).sendFromModal(r -> r.text("Sent using a manually created responder"));

return ctx.ack(); // just close the view
});

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

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

}
2 changes: 1 addition & 1 deletion bolt-servlet/src/test/java/samples/ViewSample.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static void main(String[] args) throws Exception {
AppConfig config = ResourceLoader.loadAppConfig();
App app = new App(config);

// https://github.com/slackapi/java-slack-sdk/blob/master/bolt/src/test/resources
// src/test/resources
String view1 = ResourceLoader.load("views/view1.json");
app.command("/view", (req, ctx) -> {
ViewsOpenResponse apiResponse = ctx.client().viewsOpen(r -> r
Expand Down
4 changes: 3 additions & 1 deletion bolt-servlet/src/test/java/util/ResourceLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public static AppConfig loadAppConfig(String fileName) {
String json = new BufferedReader(isr).lines().collect(joining());
JsonObject j = new Gson().fromJson(json, JsonElement.class).getAsJsonObject();
config.setSigningSecret(j.get("signingSecret").getAsString());
config.setSingleTeamBotToken(j.get("singleTeamBotToken").getAsString());
if (j.get("singleTeamBotToken") != null) {
config.setSingleTeamBotToken(j.get("singleTeamBotToken").getAsString());
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
Expand Down
64 changes: 64 additions & 0 deletions bolt-servlet/src/test/resources/views/view4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"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": "channels_select",
"action_id": "a",
"response_url_enabled": true,
"placeholder": {
"type": "plain_text",
"text": "Select a channel",
"emoji": true
}
},
"label": {
"type": "plain_text",
"text": "Label",
"emoji": true
}
},
{
"type": "input",
"block_id": "b2",
"element": {
"type": "conversations_select",
"action_id": "a",
"filter": {
"include": ["public"],
"exclude_external_shared_channels": true,
"exclude_bot_users": false
},
"placeholder": {
"type": "plain_text",
"text": "Select a channel",
"emoji": true
}
},
"label": {
"type": "plain_text",
"text": "Label",
"emoji": true
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.slack.api.bolt.context;

import com.slack.api.Slack;
import com.slack.api.app_backend.views.InputBlockResponseSender;
import com.slack.api.app_backend.views.payload.ViewSubmissionPayload;
import com.slack.api.app_backend.views.response.InputBlockResponse;
import com.slack.api.bolt.util.BuilderConfigurator;
import com.slack.api.model.block.LayoutBlock;
import com.slack.api.webhook.WebhookResponse;

import java.io.IOException;
import java.util.List;

public interface InputBlockRespondUtility {

Slack getSlack();

InputBlockResponseSender getResponder(String blockId, String actionId);

InputBlockResponseSender getFirstResponder();

List<ViewSubmissionPayload.ResponseUrl> getResponseUrls();

default WebhookResponse respond(String text) throws IOException {
return respond(InputBlockResponse.builder().text(text).build());
}

default WebhookResponse respond(List<LayoutBlock> blocks) throws IOException {
return respond(InputBlockResponse.builder().blocks(blocks).build());
}

default WebhookResponse respond(InputBlockResponse response) throws IOException {
InputBlockResponseSender responder = getFirstResponder();
if (responder == null) {
throw new IllegalStateException("This payload doesn't have response_urls. " +
"The response_urls are available only when your modal has input block elements with response_url_enabled = true.");
}
return responder.send(response);
}

default WebhookResponse respond(
BuilderConfigurator<InputBlockResponse.InputBlockResponseBuilder> builder) throws IOException {
return respond(builder.configure(InputBlockResponse.builder()).build());
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
package com.slack.api.bolt.context.builtin;

import com.slack.api.app_backend.views.InputBlockResponseSender;
import com.slack.api.app_backend.views.payload.ViewSubmissionPayload;
import com.slack.api.app_backend.views.response.ViewSubmissionResponse;
import com.slack.api.bolt.context.Context;
import com.slack.api.bolt.context.InputBlockRespondUtility;
import com.slack.api.bolt.response.Responder;
import com.slack.api.bolt.response.Response;
import com.slack.api.bolt.util.BuilderConfigurator;
import com.slack.api.model.view.View;
import lombok.*;

import java.util.List;
import java.util.Map;

@Getter
@Setter
@Builder
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
public class ViewSubmissionContext extends Context {
public class ViewSubmissionContext extends Context implements InputBlockRespondUtility {

private List<ViewSubmissionPayload.ResponseUrl> responseUrls;
private Responder responder;

public ViewSubmissionContext() {
}
Expand Down Expand Up @@ -68,4 +77,33 @@ public Response ack(String responseAction, String view) {
return ack(response);
}

@Override
public InputBlockResponseSender getResponder(String blockId, String actionId) {
if (responseUrls != null) {
for (ViewSubmissionPayload.ResponseUrl url : responseUrls) {
if (url.getBlockId().equals(blockId) && url.getActionId().equals(actionId)) {
return new InputBlockResponseSender(getSlack(), url.getResponseUrl());
}
}
}
throw new IllegalArgumentException("A response_url was not found for block_id: " + blockId + " & action_id: " + actionId);
}

@Override
public InputBlockResponseSender getFirstResponder() {
List<ViewSubmissionPayload.ResponseUrl> urls = getResponseUrls();
if (urls == null || urls.size() == 0) {
return null;
} else {
if (urls.size() > 1) {
// NOTE: As of March 2020, response_url_enabled field can be used on a single block element in a view.
// That said, the payload can have multiple URLs here. The warning message is just for possible changes in the future.
String warnMessage = "You have " + urls.size() + " `response_url`s in the payload. " +
"Context#getFirstResponder() and #respond(...) work but always use the first URL. " +
"Consider using Context#getResponder(blockId, actionId).send(...) instead.";
getLogger().warn(warnMessage);
}
return new InputBlockResponseSender(getSlack(), urls.get(0).getResponseUrl());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.slack.api.bolt.handler.builtin;

import com.slack.api.bolt.context.builtin.GlobalShortcutContext;
import com.slack.api.bolt.context.builtin.MessageShortcutContext;
import com.slack.api.bolt.handler.Handler;
import com.slack.api.bolt.request.builtin.GlobalShortcutRequest;
import com.slack.api.bolt.request.builtin.MessageShortcutRequest;
import com.slack.api.bolt.response.Response;

@FunctionalInterface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public ViewSubmissionRequest(
getContext().setEnterpriseId(payload.getTeam().getEnterpriseId());
getContext().setTeamId(payload.getTeam().getId());
getContext().setRequestUserId(payload.getUser().getId());
getContext().setResponseUrls(payload.getResponseUrls());
}

private ViewSubmissionContext context = new ViewSubmissionContext();
Expand All @@ -50,12 +51,24 @@ public RequestHeaders getHeaders() {
return this.headers;
}

public ViewSubmissionPayload getPayload() {
return payload;
}

@Override
public String getResponseUrl() {
return null;
if (payload.getResponseUrls() == null || payload.getResponseUrls().size() == 0) {
return null;
} else {
int numOfResponseUrls = payload.getResponseUrls().size();
if (numOfResponseUrls > 1) {
// NOTE: As of March 2020, response_url_enabled field can be used on a single block element in a view.
// That said, the payload can have multiple URLs here. The warning message is just for possible changes in the future.
String warnMessage = "You have " + numOfResponseUrls + " `response_url`s in the payload. " +
"ViewSubmissionRequest#getResponseUrl() always returns the first one.";
getContext().getLogger().warn(warnMessage);
}
return payload.getResponseUrls().get(0).getResponseUrl();
}
}

public ViewSubmissionPayload getPayload() {
return payload;
}
}
19 changes: 17 additions & 2 deletions bolt/src/main/java/com/slack/api/bolt/response/Responder.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.slack.api.app_backend.interactive_components.response.ActionResponse;
import com.slack.api.app_backend.slash_commands.SlashCommandResponseSender;
import com.slack.api.app_backend.slash_commands.response.SlashCommandResponse;
import com.slack.api.app_backend.views.InputBlockResponseSender;
import com.slack.api.app_backend.views.response.InputBlockResponse;
import com.slack.api.webhook.WebhookResponse;

import java.io.IOException;
Expand All @@ -20,6 +22,7 @@ public class Responder {

private final ActionResponseSender actionResponseSender;
private final SlashCommandResponseSender slashCommandResponseSender;
private final InputBlockResponseSender inputBlockResponseSender;

/**
* Initializes with a valid response_url
Expand All @@ -32,20 +35,28 @@ public Responder(Slack slack, String responseUrl) {
this.responseUrl = responseUrl;
this.actionResponseSender = new ActionResponseSender(slack);
this.slashCommandResponseSender = new SlashCommandResponseSender(slack);
this.inputBlockResponseSender = new InputBlockResponseSender(slack);
}

/**
* Sends an HTTP response for a slash command invocation.
*/
public WebhookResponse send(SlashCommandResponse response) throws IOException {
return new SlashCommandResponseSender(slack).send(responseUrl, response);
return slashCommandResponseSender.send(responseUrl, response);
}

/**
* Sends an HTTP response for an action.
*/
public WebhookResponse send(ActionResponse response) throws IOException {
return new ActionResponseSender(slack).send(responseUrl, response);
return actionResponseSender.send(responseUrl, response);
}

/**
* Sends an HTTP response for an input block in a modal.
*/
public WebhookResponse send(InputBlockResponse response) throws IOException {
return inputBlockResponseSender.send(responseUrl, response);
}

public WebhookResponse sendToAction(RequestConfigurator<ActionResponse.ActionResponseBuilder> body) throws IOException {
Expand All @@ -56,4 +67,8 @@ public WebhookResponse sendToCommand(RequestConfigurator<SlashCommandResponse.Sl
return send(body.configure(SlashCommandResponse.builder()).build());
}

public WebhookResponse sendFromModal(RequestConfigurator<InputBlockResponse.InputBlockResponseBuilder> body) throws IOException {
return send(body.configure(InputBlockResponse.builder()).build());
}

}
Loading