Skip to content

Commit 988eef7

Browse files
committed
Added support for opening the pulse modal via a slash command.
1 parent c26bac2 commit 988eef7

File tree

9 files changed

+141
-67
lines changed

9 files changed

+141
-67
lines changed

.github/workflows/gradle-build-production.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ jobs:
8888
--set-env-vars "SLACK_WEBHOOK_URL=${{ secrets.SLACK_WEBHOOK_URL }}" \
8989
--set-env-vars "SLACK_BOT_TOKEN=${{ secrets.SLACK_BOT_TOKEN }}" \
9090
--set-env-vars "SLACK_PULSE_SIGNING_SECRET=${{ secrets.SLACK_PULSE_SIGNING_SECRET }}" \
91+
--set-env-vars "SLACK_PULSE_BOT_TOKEN=${{ secrets.SLACK_PULSE_BOT_TOKEN }}" \
9192
--platform "managed" \
9293
--max-instances 8 \
9394
--allow-unauthenticated

server/src/main/java/com/objectcomputing/checkins/configuration/CheckInsConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public static class SlackConfig {
109109
private String signingSecret;
110110

111111
@NotBlank
112-
private String webhookUrl;
112+
private String botToken;
113113
}
114114
}
115115
}

server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseServicesImpl.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ private class Frequency {
4747
@Inject
4848
private PulseEmail email;
4949

50-
@Inject
51-
private PulseSlackPoster slackPoster;
52-
5350
private final DayOfWeek emailDay = DayOfWeek.MONDAY;
5451

5552
private String setting = "bi-weekly";
@@ -106,7 +103,6 @@ public void notifyUsers(LocalDate check) {
106103
if (sent.isEmpty()) {
107104
LOG.info("Sending Pulse Email");
108105
email.send();
109-
slackPoster.send();
110106
automatedEmailRepository.save(new AutomatedEmail(key));
111107
} else {
112108
LOG.info("The Pulse Email has already been sent today");

server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseSlackPoster.java

Lines changed: 0 additions & 59 deletions
This file was deleted.

server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseController.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.objectcomputing.checkins.services.pulseresponse;
22

33
import com.objectcomputing.checkins.exceptions.NotFoundException;
4+
import com.objectcomputing.checkins.util.form.FormUrlEncodedDecoder;
45
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
56

7+
import io.micronaut.http.MediaType;
68
import io.micronaut.core.annotation.Nullable;
79
import io.micronaut.core.convert.format.Format;
810
import io.micronaut.http.HttpStatus;
@@ -27,6 +29,8 @@
2729
import java.time.LocalDate;
2830
import java.util.Set;
2931
import java.util.UUID;
32+
import java.util.Map;
33+
import java.nio.charset.StandardCharsets;
3034

3135
@Controller("/services/pulse-responses")
3236
@ExecuteOn(TaskExecutors.BLOCKING)
@@ -36,13 +40,16 @@ public class PulseResponseController {
3640
private final PulseResponseService pulseResponseServices;
3741
private final MemberProfileServices memberProfileServices;
3842
private final SlackSignatureVerifier slackSignatureVerifier;
43+
private final PulseSlackCommand pulseSlackCommand;
3944

4045
public PulseResponseController(PulseResponseService pulseResponseServices,
4146
MemberProfileServices memberProfileServices,
42-
SlackSignatureVerifier slackSignatureVerifier) {
47+
SlackSignatureVerifier slackSignatureVerifier,
48+
PulseSlackCommand pulseSlackCommand) {
4349
this.pulseResponseServices = pulseResponseServices;
4450
this.memberProfileServices = memberProfileServices;
4551
this.slackSignatureVerifier = slackSignatureVerifier;
52+
this.pulseSlackCommand = pulseSlackCommand;
4653
}
4754

4855
/**
@@ -105,6 +112,33 @@ public PulseResponse readRole(@NotNull UUID id) {
105112
return result;
106113
}
107114

115+
@Secured(SecurityRule.IS_ANONYMOUS)
116+
@Post(uri = "/command", consumes = MediaType.APPLICATION_FORM_URLENCODED)
117+
public HttpResponse commandPulseResponse(
118+
@Header("X-Slack-Signature") String signature,
119+
@Header("X-Slack-Request-Timestamp") String timestamp,
120+
@Body String requestBody) {
121+
// Validate the request
122+
if (slackSignatureVerifier.verifyRequest(signature,
123+
timestamp, requestBody)) {
124+
// Convert the request body to a map of values.
125+
FormUrlEncodedDecoder formUrlEncodedDecoder = new FormUrlEncodedDecoder();
126+
Map<String, Object> body =
127+
formUrlEncodedDecoder.decode(requestBody,
128+
StandardCharsets.UTF_8);
129+
130+
// Respond to the slack command.
131+
String triggerId = (String)body.get("trigger_id");
132+
if (pulseSlackCommand.send(triggerId)) {
133+
return HttpResponse.ok();
134+
} else {
135+
return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
136+
}
137+
} else {
138+
return HttpResponse.unauthorized();
139+
}
140+
}
141+
108142
@Secured(SecurityRule.IS_ANONYMOUS)
109143
@Post("/external")
110144
public HttpResponse<PulseResponse> externalPulseResponse(
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.objectcomputing.checkins.services.pulseresponse;
2+
3+
import com.objectcomputing.checkins.configuration.CheckInsConfiguration;
4+
5+
import com.slack.api.Slack;
6+
import com.slack.api.methods.MethodsClient;
7+
import com.slack.api.methods.request.views.ViewsOpenRequest;
8+
import com.slack.api.methods.response.views.ViewsOpenResponse;
9+
10+
import io.micronaut.context.annotation.Value;
11+
import io.micronaut.core.io.Readable;
12+
import io.micronaut.core.io.IOUtils;
13+
14+
import jakarta.inject.Singleton;
15+
import jakarta.inject.Inject;
16+
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
20+
import java.io.BufferedReader;
21+
import java.util.List;
22+
23+
@Singleton
24+
public class PulseSlackCommand {
25+
private static final Logger LOG = LoggerFactory.getLogger(PulseSlackCommand.class);
26+
27+
@Inject
28+
private CheckInsConfiguration configuration;
29+
30+
@Value("classpath:slack/pulse_slack_blocks.json")
31+
private Readable pulseSlackBlocks;
32+
33+
public boolean send(String triggerId) {
34+
String slackBlocks = getSlackBlocks();
35+
36+
// See if we can have a token.
37+
String token = configuration.getApplication()
38+
.getPulseResponse()
39+
.getSlack().getBotToken();
40+
if (token != null && !slackBlocks.isEmpty()) {
41+
MethodsClient client = Slack.getInstance().methods(token);
42+
43+
try {
44+
ViewsOpenRequest request = ViewsOpenRequest.builder()
45+
.triggerId(triggerId)
46+
.viewAsString(slackBlocks)
47+
.build();
48+
49+
// Send it to Slack
50+
ViewsOpenResponse response = client.viewsOpen(request);
51+
52+
if (!response.isOk()) {
53+
LOG.error("Unable to open the Pulse view");
54+
}
55+
56+
return response.isOk();
57+
} catch(Exception ex) {
58+
LOG.error(ex.toString());
59+
return false;
60+
}
61+
} else {
62+
LOG.error("Missing token or missing slack blocks");
63+
return false;
64+
}
65+
}
66+
67+
private String getSlackBlocks() {
68+
try {
69+
return IOUtils.readText(
70+
new BufferedReader(pulseSlackBlocks.asReader()));
71+
} catch(Exception ex) {
72+
LOG.error(ex.toString());
73+
return "";
74+
}
75+
}
76+
}
77+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.objectcomputing.checkins.util.form;
2+
3+
import java.nio.charset.Charset;
4+
import java.net.URLDecoder;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import jakarta.inject.Singleton;
9+
10+
@Singleton
11+
public class FormUrlEncodedDecoder {
12+
public Map<String, Object> decode(String formUrlEncodedString, Charset charset) {
13+
Map<String, Object> queryParams = new HashMap<>();
14+
String[] pairs = formUrlEncodedString.split("&");
15+
for (String pair : pairs) {
16+
int idx = pair.indexOf("=");
17+
if (idx > 0) {
18+
String key = URLDecoder.decode(pair.substring(0, idx), charset);
19+
String value = URLDecoder.decode(pair.substring(idx + 1), charset);
20+
queryParams.put(key, value);
21+
}
22+
}
23+
return queryParams;
24+
}
25+
}

server/src/main/resources/application.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ check-ins:
104104
pulse-response:
105105
slack:
106106
signing-secret: ${ SLACK_PULSE_SIGNING_SECRET }
107-
webhook-url: ${ SLACK_PULSE_WEBHOOK_URL }
107+
bot-token: ${ SLACK_PULSE_BOT_TOKEN }
108108
web-address: ${ WEB_ADDRESS }
109109
---
110110
flyway:

server/src/test/resources/application-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ check-ins:
4848
pulse-response:
4949
slack:
5050
signing-secret: BOGUS_SIGNING_SECRET
51-
webhook-url: https://bogus.objectcomputing.com/slack
51+
bot-token: BOGUS_TOKEN
5252
---
5353
aes:
5454
key: BOGUS_TEST_KEY

0 commit comments

Comments
 (0)