Skip to content

Commit 4ede442

Browse files
committed
Moved handling of Slack submissions, added code to read slack messages, persist automated kudos.
1 parent 0c12da8 commit 4ede442

19 files changed

+711
-74
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ public static class SlackConfig {
8282

8383
@NotBlank
8484
private String signingSecret;
85+
86+
@NotBlank
87+
private String kudosChannel;
8588
}
8689
}
8790
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.objectcomputing.checkins.notifications.social_media;
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.chat.ChatPostMessageRequest;
8+
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
9+
import com.slack.api.methods.request.conversations.ConversationsOpenRequest;
10+
import com.slack.api.methods.response.conversations.ConversationsOpenResponse;
11+
12+
import jakarta.inject.Singleton;
13+
import jakarta.inject.Inject;
14+
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
18+
import java.util.List;
19+
20+
@Singleton
21+
public class SlackSender {
22+
private static final Logger LOG = LoggerFactory.getLogger(SlackSender.class);
23+
24+
@Inject
25+
private CheckInsConfiguration configuration;
26+
27+
public boolean send(List<String> userIds, String slackBlocks) {
28+
// See if we have a token.
29+
String token = configuration.getApplication()
30+
.getSlack().getBotToken();
31+
if (token != null && !slackBlocks.isEmpty()) {
32+
MethodsClient client = Slack.getInstance().methods(token);
33+
34+
try {
35+
ConversationsOpenResponse openResponse =
36+
client.conversationsOpen(ConversationsOpenRequest.builder()
37+
.users(userIds)
38+
.returnIm(true)
39+
.build());
40+
if (!openResponse.isOk()) {
41+
LOG.error("Unable to open the conversation");
42+
return false;
43+
}
44+
45+
ChatPostMessageRequest request = ChatPostMessageRequest
46+
.builder()
47+
.channel(openResponse.getChannel().getId())
48+
.blocksAsString(slackBlocks)
49+
.text("This is a test")
50+
.build();
51+
52+
// Send it to Slack
53+
ChatPostMessageResponse response = client.chatPostMessage(request);
54+
55+
if (!response.isOk()) {
56+
LOG.error("Unable to send the chat message: " +
57+
response.getError());
58+
}
59+
60+
return response.isOk();
61+
} catch(Exception ex) {
62+
LOG.error("SlackSender.send: " + ex.toString());
63+
return false;
64+
}
65+
} else {
66+
LOG.error("Missing token or missing slack blocks");
67+
return false;
68+
}
69+
}
70+
}
71+

server/src/main/java/com/objectcomputing/checkins/services/kudos/KudosServices.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public interface KudosServices {
1111

1212
Kudos approve(Kudos kudos);
1313

14+
Kudos savePreapproved(KudosCreateDTO kudos);
15+
1416
List<KudosResponseDTO> getRecent();
1517

1618
KudosResponseDTO getById(UUID id);

server/src/main/java/com/objectcomputing/checkins/services/kudos/KudosServicesImpl.java

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -93,31 +93,7 @@ private enum NotificationType {
9393
@Transactional
9494
@RequiredPermission(Permission.CAN_CREATE_KUDOS)
9595
public Kudos save(KudosCreateDTO kudosDTO) {
96-
UUID senderId = kudosDTO.getSenderId();
97-
if (memberProfileRetrievalServices.getById(senderId).isEmpty()) {
98-
throw new BadArgException("Kudos sender %s does not exist".formatted(senderId));
99-
}
100-
101-
if (kudosDTO.getTeamId() != null) {
102-
UUID teamId = kudosDTO.getTeamId();
103-
if (teamRepository.findById(teamId).isEmpty()) {
104-
throw new BadArgException("Team %s does not exist".formatted(teamId));
105-
}
106-
}
107-
108-
if (kudosDTO.getRecipientMembers() == null || kudosDTO.getRecipientMembers().isEmpty()) {
109-
throw new BadArgException("Kudos must contain at least one recipient");
110-
}
111-
112-
Kudos kudos = new Kudos(kudosDTO);
113-
114-
Kudos savedKudos = kudosRepository.save(kudos);
115-
116-
for (MemberProfile recipient : kudosDTO.getRecipientMembers()) {
117-
KudosRecipient kudosRecipient = new KudosRecipient(savedKudos.getId(), recipient.getId());
118-
kudosRecipientServices.save(kudosRecipient);
119-
}
120-
96+
Kudos savedKudos = saveCommon(kudosDTO, true);
12197
sendNotification(savedKudos, NotificationType.creation);
12298
return savedKudos;
12399
}
@@ -140,6 +116,13 @@ public Kudos approve(Kudos kudos) {
140116
return updated;
141117
}
142118

119+
@Override
120+
public Kudos savePreapproved(KudosCreateDTO kudos) {
121+
Kudos savedKudos = saveCommon(kudos, false);
122+
savedKudos.setDateApproved(LocalDate.now());
123+
return kudosRepository.update(savedKudos);
124+
}
125+
143126
@Override
144127
public KudosResponseDTO getById(UUID id) {
145128

@@ -387,4 +370,38 @@ private void slackApprovedKudos(Kudos kudos) {
387370
private boolean hasAdministerKudosPermission() {
388371
return currentUserServices.hasPermission(Permission.CAN_ADMINISTER_KUDOS);
389372
}
373+
374+
private Kudos saveCommon(KudosCreateDTO kudosDTO, boolean verifyAndNotify) {
375+
UUID senderId = kudosDTO.getSenderId();
376+
if (memberProfileRetrievalServices.getById(senderId).isEmpty()) {
377+
throw new BadArgException("Kudos sender %s does not exist".formatted(senderId));
378+
}
379+
380+
if (kudosDTO.getTeamId() != null) {
381+
UUID teamId = kudosDTO.getTeamId();
382+
if (teamRepository.findById(teamId).isEmpty()) {
383+
throw new BadArgException("Team %s does not exist".formatted(teamId));
384+
}
385+
}
386+
387+
if (kudosDTO.getRecipientMembers() == null || kudosDTO.getRecipientMembers().isEmpty()) {
388+
throw new BadArgException("Kudos must contain at least one recipient");
389+
}
390+
391+
Kudos savedKudos = kudosRepository.save(new Kudos(kudosDTO));
392+
393+
for (MemberProfile recipient : kudosDTO.getRecipientMembers()) {
394+
KudosRecipient kudosRecipient = new KudosRecipient(savedKudos.getId(), recipient.getId());
395+
if (verifyAndNotify) {
396+
// Going through the service verifies the sender and recipient
397+
// and sends email notification after saving.
398+
kudosRecipientServices.save(kudosRecipient);
399+
} else {
400+
// This does none of that and just stores it in the database.
401+
kudosRecipientRepository.save(kudosRecipient);
402+
}
403+
}
404+
405+
return savedKudos;
406+
}
390407
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.objectcomputing.checkins.services.slack;
2+
3+
import com.objectcomputing.checkins.configuration.CheckInsConfiguration;
4+
5+
import com.slack.api.Slack;
6+
import com.slack.api.model.Message;
7+
import com.slack.api.model.Conversation;
8+
import com.slack.api.methods.MethodsClient;
9+
import com.slack.api.methods.SlackApiException;
10+
import com.slack.api.methods.request.conversations.ConversationsHistoryRequest;
11+
import com.slack.api.methods.response.conversations.ConversationsHistoryResponse;
12+
13+
import jakarta.inject.Singleton;
14+
import jakarta.inject.Inject;
15+
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
19+
import java.util.List;
20+
import java.util.ArrayList;
21+
import java.io.IOException;
22+
import java.time.ZoneOffset;
23+
import java.time.LocalDateTime;
24+
import java.time.ZoneId;
25+
26+
@Singleton
27+
public class SlackReader {
28+
private static final Logger LOG = LoggerFactory.getLogger(SlackReader.class);
29+
30+
@Inject
31+
private CheckInsConfiguration configuration;
32+
33+
public List<Message> read(String channelId, LocalDateTime last) {
34+
String token = configuration.getApplication().getSlack().getBotToken();
35+
if (token != null) {
36+
try {
37+
long ts = last.atZone(ZoneId.systemDefault())
38+
.toInstant().getEpochSecond();
39+
String timestamp = String.valueOf(ts);
40+
MethodsClient client = Slack.getInstance().methods(token);
41+
ConversationsHistoryResponse response =
42+
client.conversationsHistory(
43+
ConversationsHistoryRequest.builder()
44+
.channel(channelId)
45+
.oldest(timestamp)
46+
.inclusive(true)
47+
.build());
48+
49+
if (response.isOk()) {
50+
return response.getMessages();
51+
} else {
52+
LOG.error("Slack Response: " + response.getError() +
53+
" - " + response.getNeeded());
54+
}
55+
} catch(IOException e) {
56+
LOG.error("SlackReader.read: " + e.toString());
57+
} catch(SlackApiException e) {
58+
LOG.error("SlackReader.read: " + e.toString());
59+
}
60+
} else {
61+
LOG.error("Slack Token not available");
62+
}
63+
return new ArrayList<Message>();
64+
}
65+
}
66+

server/src/main/java/com/objectcomputing/checkins/services/slack/SlackSubmissionHandler.java

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.objectcomputing.checkins.services.slack;
22

3+
import com.objectcomputing.checkins.services.slack.pulseresponse.PulseSlackCommand;
4+
import com.objectcomputing.checkins.services.slack.pulseresponse.SlackPulseResponseConverter;
5+
36
import com.objectcomputing.checkins.util.form.FormUrlEncodedDecoder;
47
import com.objectcomputing.checkins.services.pulseresponse.PulseResponse;
58
import com.objectcomputing.checkins.services.pulseresponse.PulseResponseService;
@@ -11,6 +14,10 @@
1114

1215
import jakarta.inject.Singleton;
1316

17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
import com.fasterxml.jackson.core.type.TypeReference;
19+
import com.fasterxml.jackson.core.JsonProcessingException;
20+
1421
import java.util.Map;
1522
import java.nio.charset.StandardCharsets;
1623

@@ -71,40 +78,73 @@ public HttpResponse externalResponse(String signature,
7178

7279
final String key = "payload";
7380
if (body.containsKey(key)) {
74-
PulseResponseCreateDTO pulseResponseDTO =
75-
slackPulseResponseConverter.get((String)body.get(key));
76-
77-
// If we receive a null DTO, that means that this is not the
78-
// actual submission of the form. We can just return 200 so
79-
// that Slack knows to continue without error.
80-
if (pulseResponseDTO == null) {
81-
return HttpResponse.ok();
81+
try {
82+
final ObjectMapper mapper = new ObjectMapper();
83+
final Map<String, Object> map =
84+
mapper.readValue((String)body.get(key),
85+
new TypeReference<>() {});
86+
if (isPulseSubmission(map)) {
87+
return completePulse(map);
88+
}
89+
} catch(JsonProcessingException ex) {
90+
// Fall through to the bottom...
8291
}
83-
84-
// Create the pulse response
85-
PulseResponse pulseResponse =
86-
pulseResponseServices.unsecureSave(
87-
new PulseResponse(
88-
pulseResponseDTO.getInternalScore(),
89-
pulseResponseDTO.getExternalScore(),
90-
pulseResponseDTO.getSubmissionDate(),
91-
pulseResponseDTO.getTeamMemberId(),
92-
pulseResponseDTO.getInternalFeelings(),
93-
pulseResponseDTO.getExternalFeelings()
94-
)
95-
);
96-
97-
if (pulseResponse == null) {
98-
return HttpResponse.status(HttpStatus.CONFLICT,
99-
"Already submitted today");
100-
} else {
101-
return HttpResponse.ok();
102-
}
103-
} else {
104-
return HttpResponse.unprocessableEntity();
10592
}
10693
} else {
10794
return HttpResponse.unauthorized();
10895
}
96+
97+
return HttpResponse.unprocessableEntity();
98+
}
99+
100+
private boolean isPulseSubmission(Map<String, Object> map) {
101+
final String typeKey = "type";
102+
if (map.containsKey(typeKey)) {
103+
final String type = (String)map.get(typeKey);
104+
if (type.equals("view_submission")) {
105+
final String viewKey = "view";
106+
if (map.containsKey(viewKey)) {
107+
final Map<String, Object> view =
108+
(Map<String, Object>)map.get(viewKey);
109+
final String callbackKey = "callback_id";
110+
if (view.containsKey(callbackKey)) {
111+
return "pulseSubmission".equals(view.get(callbackKey));
112+
}
113+
}
114+
}
115+
}
116+
return false;
117+
}
118+
119+
private HttpResponse completePulse(Map<String, Object> map) {
120+
PulseResponseCreateDTO pulseResponseDTO =
121+
slackPulseResponseConverter.get(map);
122+
123+
// If we receive a null DTO, that means that this is not the actual
124+
// submission of the form. We can just return 200 so that Slack knows
125+
// to continue without error. Realy, this should not happen. But, just
126+
// in case...
127+
if (pulseResponseDTO != null) {
128+
// Create the pulse response
129+
PulseResponse pulseResponse =
130+
pulseResponseServices.unsecureSave(
131+
new PulseResponse(
132+
pulseResponseDTO.getInternalScore(),
133+
pulseResponseDTO.getExternalScore(),
134+
pulseResponseDTO.getSubmissionDate(),
135+
pulseResponseDTO.getTeamMemberId(),
136+
pulseResponseDTO.getInternalFeelings(),
137+
pulseResponseDTO.getExternalFeelings()
138+
)
139+
);
140+
141+
if (pulseResponse == null) {
142+
// If pulse response is null, that means that this user has
143+
// already submitted a response today.
144+
return HttpResponse.status(HttpStatus.CONFLICT,
145+
"Already submitted today");
146+
}
147+
}
148+
return HttpResponse.ok();
109149
}
110150
}

0 commit comments

Comments
 (0)