Skip to content

Commit c8a06b1

Browse files
committed
Merge branch 'develop' into feature-2854/slack-integration-per-deployment
2 parents e2c88a5 + 8b3855d commit c8a06b1

File tree

9 files changed

+254
-139
lines changed

9 files changed

+254
-139
lines changed

server/src/main/java/com/objectcomputing/checkins/notifications/social_media/SlackSearch.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import com.slack.api.methods.response.conversations.ConversationsListResponse;
1111
import com.slack.api.methods.request.users.UsersLookupByEmailRequest;
1212
import com.slack.api.methods.response.users.UsersLookupByEmailResponse;
13+
import com.slack.api.methods.request.users.UsersInfoRequest;
14+
import com.slack.api.methods.response.users.UsersInfoResponse;
1315

1416
import jakarta.inject.Singleton;
1517
import jakarta.inject.Inject;
@@ -76,5 +78,26 @@ public String findUserId(String userEmail) {
7678
}
7779
return null;
7880
}
81+
82+
public String findUserEmail(String userId) {
83+
String token = configuration.getApplication().getNotifications().getSlack().getBotToken();
84+
if (token != null) {
85+
try {
86+
MethodsClient client = Slack.getInstance().methods(token);
87+
UsersInfoResponse response = client.usersInfo(
88+
UsersInfoRequest.builder().user(userId).build()
89+
);
90+
91+
if (response.isOk()) {
92+
return response.getUser().getProfile().getEmail();
93+
}
94+
} catch(IOException e) {
95+
LOG.error("SlackSearch.findUserEmail: " + e.toString());
96+
} catch(SlackApiException e) {
97+
LOG.error("SlackSearch.findUserEmail: " + e.toString());
98+
}
99+
}
100+
return null;
101+
}
79102
}
80103

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

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public class PulseResponse {
7777
protected PulseResponse() {
7878
}
7979

80-
public PulseResponse(UUID id, Integer internalScore, @Nullable Integer externalScore, LocalDate submissionDate, @Nullable UUID teamMemberId, String internalFeelings, String externalFeelings) {
80+
public PulseResponse(UUID id, Integer internalScore, @Nullable Integer externalScore, LocalDate submissionDate, @Nullable UUID teamMemberId, @Nullable String internalFeelings, @Nullable String externalFeelings) {
8181
this.id = id;
8282
this.internalScore = internalScore;
8383
this.externalScore = externalScore;
@@ -91,62 +91,6 @@ public PulseResponse(Integer internalScore, Integer externalScore, LocalDate sub
9191
this(null, internalScore, externalScore, submissionDate, teamMemberId, internalFeelings, externalFeelings);
9292
}
9393

94-
public UUID getId() {
95-
return this.id;
96-
}
97-
98-
public void setId(UUID id) {
99-
this.id = id;
100-
}
101-
102-
public Integer getInternalScore() {
103-
return internalScore;
104-
}
105-
106-
public void setInternalScore(Integer internalScore) {
107-
this.internalScore = internalScore;
108-
}
109-
110-
public Integer getExternalScore() {
111-
return externalScore;
112-
}
113-
114-
public void setExternalScore(Integer externalScore) {
115-
this.externalScore = externalScore;
116-
}
117-
118-
public LocalDate getSubmissionDate() {
119-
return submissionDate;
120-
}
121-
122-
public void setSubmissionDate(LocalDate submissionDate) {
123-
this.submissionDate = submissionDate;
124-
}
125-
126-
public UUID getTeamMemberId() {
127-
return this.teamMemberId;
128-
}
129-
130-
public void setTeamMemberId(UUID teamMemberId) {
131-
this.teamMemberId = teamMemberId;
132-
}
133-
134-
public String getInternalFeelings() {
135-
return internalFeelings;
136-
}
137-
138-
public void setInternalFeelings(String internalFeelings) {
139-
this.internalFeelings = internalFeelings;
140-
}
141-
142-
public String getExternalFeelings() {
143-
return externalFeelings;
144-
}
145-
146-
public void setExternalFeelings(String externalFeelings) {
147-
this.externalFeelings = externalFeelings;
148-
}
149-
15094
@Override
15195
public boolean equals(Object o) {
15296
if (this == o) return true;

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

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525
import jakarta.validation.Valid;
2626
import jakarta.validation.constraints.NotNull;
2727

28-
import org.slf4j.Logger;
29-
import org.slf4j.LoggerFactory;
30-
3128
import java.net.URI;
3229
import java.time.LocalDate;
3330
import java.util.Set;
@@ -39,21 +36,22 @@
3936
@ExecuteOn(TaskExecutors.BLOCKING)
4037
@Tag(name = "pulse-responses")
4138
public class PulseResponseController {
42-
private static final Logger LOG = LoggerFactory.getLogger(PulseResponseController.class);
43-
4439
private final PulseResponseService pulseResponseServices;
4540
private final MemberProfileServices memberProfileServices;
4641
private final SlackSignatureVerifier slackSignatureVerifier;
4742
private final PulseSlackCommand pulseSlackCommand;
43+
private final SlackPulseResponseConverter slackPulseResponseConverter;
4844

4945
public PulseResponseController(PulseResponseService pulseResponseServices,
5046
MemberProfileServices memberProfileServices,
5147
SlackSignatureVerifier slackSignatureVerifier,
52-
PulseSlackCommand pulseSlackCommand) {
48+
PulseSlackCommand pulseSlackCommand,
49+
SlackPulseResponseConverter slackPulseResponseConverter) {
5350
this.pulseResponseServices = pulseResponseServices;
5451
this.memberProfileServices = memberProfileServices;
5552
this.slackSignatureVerifier = slackSignatureVerifier;
5653
this.pulseSlackCommand = pulseSlackCommand;
54+
this.slackPulseResponseConverter = slackPulseResponseConverter;
5755
}
5856

5957
/**
@@ -145,48 +143,55 @@ public HttpResponse commandPulseResponse(
145143

146144
@Secured(SecurityRule.IS_ANONYMOUS)
147145
@Post(uri = "/external", consumes = MediaType.APPLICATION_FORM_URLENCODED)
148-
public HttpResponse<PulseResponse> externalPulseResponse(
146+
public HttpResponse externalPulseResponse(
149147
@Header("X-Slack-Signature") String signature,
150148
@Header("X-Slack-Request-Timestamp") String timestamp,
151149
@Body String requestBody,
152150
HttpRequest<?> request) {
153-
// DEBUG Only
154-
LOG.info(requestBody);
155-
156151
// Validate the request
157152
if (slackSignatureVerifier.verifyRequest(signature,
158153
timestamp, requestBody)) {
159-
// DEBUG Only
160-
LOG.info("Request has been verified");
161-
162-
PulseResponseCreateDTO pulseResponseDTO =
163-
SlackPulseResponseConverter.get(memberProfileServices,
164-
requestBody);
165-
166-
// DEBUG Only
167-
LOG.info("Request has been converted");
168-
169-
// Create the pulse response
170-
PulseResponse pulseResponse = pulseResponseServices.unsecureSave(
171-
new PulseResponse(
172-
pulseResponseDTO.getInternalScore(),
173-
pulseResponseDTO.getExternalScore(),
174-
pulseResponseDTO.getSubmissionDate(),
175-
pulseResponseDTO.getTeamMemberId(),
176-
pulseResponseDTO.getInternalFeelings(),
177-
pulseResponseDTO.getExternalFeelings()
178-
)
179-
);
180-
181-
if (pulseResponse == null) {
182-
return HttpResponse.status(HttpStatus.CONFLICT,
183-
"Already submitted today");
154+
// Convert the request body to a map of values.
155+
FormUrlEncodedDecoder formUrlEncodedDecoder =
156+
new FormUrlEncodedDecoder();
157+
Map<String, Object> body =
158+
formUrlEncodedDecoder.decode(requestBody,
159+
StandardCharsets.UTF_8);
160+
161+
final String key = "payload";
162+
if (body.containsKey(key)) {
163+
PulseResponseCreateDTO pulseResponseDTO =
164+
slackPulseResponseConverter.get(memberProfileServices,
165+
(String)body.get(key));
166+
167+
// If we receive a null DTO, that means that this is not the
168+
// actual submission of the form. We can just return 200 so
169+
// that Slack knows to continue without error.
170+
if (pulseResponseDTO == null) {
171+
return HttpResponse.ok();
172+
}
173+
174+
// Create the pulse response
175+
PulseResponse pulseResponse =
176+
pulseResponseServices.unsecureSave(
177+
new PulseResponse(
178+
pulseResponseDTO.getInternalScore(),
179+
pulseResponseDTO.getExternalScore(),
180+
pulseResponseDTO.getSubmissionDate(),
181+
pulseResponseDTO.getTeamMemberId(),
182+
pulseResponseDTO.getInternalFeelings(),
183+
pulseResponseDTO.getExternalFeelings()
184+
)
185+
);
186+
187+
if (pulseResponse == null) {
188+
return HttpResponse.status(HttpStatus.CONFLICT,
189+
"Already submitted today");
190+
} else {
191+
return HttpResponse.ok();
192+
}
184193
} else {
185-
return HttpResponse.created(pulseResponse)
186-
.headers(headers -> headers.location(
187-
URI.create(String.format("%s/%s",
188-
request.getPath(),
189-
pulseResponse.getId()))));
194+
return HttpResponse.unprocessableEntity();
190195
}
191196
} else {
192197
return HttpResponse.unauthorized();

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

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import com.objectcomputing.checkins.exceptions.BadArgException;
44
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
55
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
6+
import com.objectcomputing.checkins.notifications.social_media.SlackSearch;
7+
8+
import jakarta.inject.Singleton;
69

710
import org.slf4j.Logger;
811
import org.slf4j.LoggerFactory;
@@ -15,20 +18,26 @@
1518
import java.util.UUID;
1619
import java.time.LocalDate;
1720

21+
@Singleton
1822
public class SlackPulseResponseConverter {
1923
private static final Logger LOG = LoggerFactory.getLogger(SlackPulseResponseConverter.class);
2024

21-
public static PulseResponseCreateDTO get(
25+
private final SlackSearch slackSearch;
26+
27+
public SlackPulseResponseConverter(SlackSearch slackSearch) {
28+
this.slackSearch = slackSearch;
29+
}
30+
31+
public PulseResponseCreateDTO get(
2232
MemberProfileServices memberProfileServices, String body) {
23-
final String key = "payload=";
24-
final int start = body.indexOf(key);
25-
if (start >= 0) {
26-
try {
27-
// Get the map of values from the string body
28-
final ObjectMapper mapper = new ObjectMapper();
29-
final Map<String, Object> map =
30-
mapper.readValue(body.substring(start + key.length()),
31-
new TypeReference<>() {});
33+
try {
34+
// Get the map of values from the string body
35+
final ObjectMapper mapper = new ObjectMapper();
36+
final Map<String, Object> map =
37+
mapper.readValue(body, new TypeReference<>() {});
38+
final String type = (String)map.get("type");
39+
40+
if (type.equals("view_submission")) {
3241
final Map<String, Object> view =
3342
(Map<String, Object>)map.get("view");
3443
final Map<String, Object> state =
@@ -41,59 +50,77 @@ public static PulseResponseCreateDTO get(
4150
response.setTeamMemberId(lookupUser(memberProfileServices, map));
4251
response.setSubmissionDate(LocalDate.now());
4352

44-
response.setInternalScore(Integer.parseInt(
45-
getMappedValue(values, "internalScore", true)));
46-
response.setInternalFeelings(
47-
getMappedValue(values, "internalFeelings", false));
53+
// Internal Score
54+
Map<String, Object> internalBlock =
55+
(Map<String, Object>)values.get("internalNumber");
56+
response.setInternalScore(Integer.parseInt(getMappedValue(
57+
internalBlock, "internalScore", "selected_option", true)));
58+
// Internal Feelings
59+
response.setInternalFeelings(getMappedValue(
60+
values, "internalText", "internalFeelings", false));
4861

49-
String score = getMappedValue(values, "externalScore", false);
50-
if (!score.isEmpty()) {
62+
// External Score
63+
Map<String, Object> externalBlock =
64+
(Map<String, Object>)values.get("externalNumber");
65+
String score = getMappedValue(externalBlock, "externalScore",
66+
"selected_option", false);
67+
if (score != null && !score.isEmpty()) {
5168
response.setExternalScore(Integer.parseInt(score));
5269
}
53-
response.setExternalFeelings(
54-
getMappedValue(values, "externalFeelings", false));
70+
// External Feelings
71+
response.setExternalFeelings(getMappedValue(
72+
values, "externalText", "externalFeelings", false));
5573

5674
return response;
57-
} catch(JsonProcessingException ex) {
58-
LOG.error(ex.getMessage());
59-
throw new BadArgException(ex.getMessage());
60-
} catch(NumberFormatException ex) {
61-
LOG.error(ex.getMessage());
62-
throw new BadArgException("Pulse scores must be integers");
75+
} else {
76+
// If it's not a view submission, we need to return null so
77+
// the the caller knows that this is not the full pulse
78+
// response.
79+
return null;
6380
}
64-
} else {
65-
LOG.error(body);
66-
throw new BadArgException("Invalid pulse response body");
81+
} catch(JsonProcessingException ex) {
82+
LOG.error(ex.getMessage());
83+
throw new BadArgException(ex.getMessage());
84+
} catch(NumberFormatException ex) {
85+
LOG.error(ex.getMessage());
86+
throw new BadArgException("Pulse scores must be integers");
6787
}
6888
}
6989

70-
private static String getMappedValue(Map<String, Object> map,
71-
String key, boolean required) {
90+
private String getMappedValue(Map<String, Object> map, String key1,
91+
String key2, boolean required) {
7292
final String valueKey = "value";
73-
if (map.containsKey(key)) {
74-
final Map<String, Object> other = (Map<String, Object>)map.get(key);
75-
if (other.containsKey(valueKey)) {
76-
return (String)other.get(valueKey);
93+
if (map != null && map.containsKey(key1)) {
94+
Map<String, Object> firstMap = (Map<String, Object>)map.get(key1);
95+
if (firstMap != null && firstMap.containsKey(key2)) {
96+
final Map<String, Object> secondMap =
97+
(Map<String, Object>)firstMap.get(key2);
98+
if (secondMap != null && secondMap.containsKey(valueKey)) {
99+
return (String)secondMap.get(valueKey);
100+
}
77101
}
78102
}
79103

80104
if (required) {
81-
LOG.error("Expected {}.{} was not found", key, valueKey);
105+
LOG.error("Expected {}.{}.{} was not found", key1, key2, valueKey);
82106
throw new BadArgException(
83-
String.format("Expected %s.%s was not found", key, valueKey));
107+
String.format("Expected %s.%s.%s was not found",
108+
key1, key2, valueKey));
84109
} else {
85-
return "";
110+
return null;
86111
}
87112
}
88113

89-
private static UUID lookupUser(MemberProfileServices memberProfileServices,
90-
Map<String, Object> map) {
114+
private UUID lookupUser(MemberProfileServices memberProfileServices,
115+
Map<String, Object> map) {
91116
// Get the user's profile map.
92117
Map<String, Object> user = (Map<String, Object>)map.get("user");
93-
Map<String, Object> profile = (Map<String, Object>)user.get("profile");
94118

95119
// Lookup the user based on the email address.
96-
String email = (String)profile.get("email");
120+
String email = slackSearch.findUserEmail((String)user.get("id"));
121+
if (email == null) {
122+
throw new BadArgException("Unable to find the user email address");
123+
}
97124
MemberProfile member = memberProfileServices.findByWorkEmail(email);
98125
return member.getId();
99126
}

0 commit comments

Comments
 (0)