Skip to content

Commit 8e90f43

Browse files
committed
Merge branch 'develop' into feature-2834/move-required-permission-to-services
2 parents 9be0d53 + 8b3855d commit 8e90f43

26 files changed

+826
-90
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ jobs:
8787
--set-env-vars "^@^MICRONAUT_ENVIRONMENTS=cloud,google,gcp" \
8888
--set-env-vars "SLACK_WEBHOOK_URL=${{ secrets.SLACK_WEBHOOK_URL }}" \
8989
--set-env-vars "SLACK_BOT_TOKEN=${{ secrets.SLACK_BOT_TOKEN }}" \
90+
--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 }}" \
9092
--platform "managed" \
9193
--max-instances 8 \
9294
--allow-unauthenticated

.github/workflows/gradle-deploy-develop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ jobs:
112112
--set-env-vars "^@^MICRONAUT_ENVIRONMENTS=dev,cloud,google,gcp" \
113113
--set-env-vars "SLACK_WEBHOOK_URL=${{ secrets.SLACK_WEBHOOK_URL }}" \
114114
--set-env-vars "SLACK_BOT_TOKEN=${{ secrets.SLACK_BOT_TOKEN }}" \
115+
--set-env-vars "SLACK_PULSE_SIGNING_SECRET=${{ secrets.SLACK_PULSE_SIGNING_SECRET }}" \
116+
--set-env-vars "SLACK_PULSE_BOT_TOKEN=${{ secrets.SLACK_PULSE_BOT_TOKEN }}" \
115117
--platform "managed" \
116118
--max-instances 2 \
117119
--allow-unauthenticated

.github/workflows/gradle-deploy-native-develop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ jobs:
111111
--set-env-vars "^@^MICRONAUT_ENVIRONMENTS=dev,cloud,google,gcp" \
112112
--set-env-vars "SLACK_WEBHOOK_URL=${{ secrets.SLACK_WEBHOOK_URL }}" \
113113
--set-env-vars "SLACK_BOT_TOKEN=${{ secrets.SLACK_BOT_TOKEN }}" \
114+
--set-env-vars "SLACK_PULSE_SIGNING_SECRET=${{ secrets.SLACK_PULSE_SIGNING_SECRET }}" \
115+
--set-env-vars "SLACK_PULSE_BOT_TOKEN=${{ secrets.SLACK_PULSE_BOT_TOKEN }}" \
114116
--platform "managed" \
115117
--max-instances 2 \
116118
--allow-unauthenticated

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public static class ApplicationConfig {
3434
@NotNull
3535
private NotificationsConfig notifications;
3636

37+
@NotNull
38+
private PulseResponseConfig pulseResponse;
39+
3740
@Getter
3841
@Setter
3942
@ConfigurationProperties("feedback")
@@ -89,5 +92,25 @@ public static class SlackConfig {
8992
private String botToken;
9093
}
9194
}
95+
96+
@Getter
97+
@Setter
98+
@ConfigurationProperties("pulse-response")
99+
public static class PulseResponseConfig {
100+
101+
@NotNull
102+
private SlackConfig slack;
103+
104+
@Getter
105+
@Setter
106+
@ConfigurationProperties("slack")
107+
public static class SlackConfig {
108+
@NotBlank
109+
private String signingSecret;
110+
111+
@NotBlank
112+
private String botToken;
113+
}
114+
}
92115
}
93116
}

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/pulse/PulseServices.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
import java.time.LocalDate;
44

55
public interface PulseServices {
6-
public void sendPendingEmail(LocalDate now);
6+
public void notifyUsers(LocalDate now);
77
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public PulseServicesImpl(
6969
this.automatedEmailRepository = automatedEmailRepository;
7070
}
7171

72-
public void sendPendingEmail(LocalDate check) {
72+
public void notifyUsers(LocalDate check) {
7373
if (check.getDayOfWeek() == emailDay) {
7474
LOG.info("Checking for pending Pulse email");
7575
// Start from the first of the year and move forward to ensure that we

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: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package com.objectcomputing.checkins.services.pulseresponse;
22

33
import com.objectcomputing.checkins.exceptions.NotFoundException;
4+
import com.objectcomputing.checkins.util.form.FormUrlEncodedDecoder;
5+
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
6+
7+
import io.micronaut.http.MediaType;
48
import io.micronaut.core.annotation.Nullable;
59
import io.micronaut.core.convert.format.Format;
10+
import io.micronaut.http.HttpStatus;
611
import io.micronaut.http.HttpRequest;
712
import io.micronaut.http.HttpResponse;
13+
import io.micronaut.http.annotation.Header;
814
import io.micronaut.http.annotation.Body;
915
import io.micronaut.http.annotation.Controller;
1016
import io.micronaut.http.annotation.Get;
@@ -14,6 +20,7 @@
1420
import io.micronaut.scheduling.annotation.ExecuteOn;
1521
import io.micronaut.security.annotation.Secured;
1622
import io.micronaut.security.rules.SecurityRule;
23+
1724
import io.swagger.v3.oas.annotations.tags.Tag;
1825
import jakarta.validation.Valid;
1926
import jakarta.validation.constraints.NotNull;
@@ -22,17 +29,29 @@
2229
import java.time.LocalDate;
2330
import java.util.Set;
2431
import java.util.UUID;
32+
import java.util.Map;
33+
import java.nio.charset.StandardCharsets;
2534

2635
@Controller("/services/pulse-responses")
2736
@ExecuteOn(TaskExecutors.BLOCKING)
28-
@Secured(SecurityRule.IS_AUTHENTICATED)
2937
@Tag(name = "pulse-responses")
3038
public class PulseResponseController {
31-
3239
private final PulseResponseService pulseResponseServices;
40+
private final MemberProfileServices memberProfileServices;
41+
private final SlackSignatureVerifier slackSignatureVerifier;
42+
private final PulseSlackCommand pulseSlackCommand;
43+
private final SlackPulseResponseConverter slackPulseResponseConverter;
3344

34-
public PulseResponseController(PulseResponseService pulseResponseServices) {
45+
public PulseResponseController(PulseResponseService pulseResponseServices,
46+
MemberProfileServices memberProfileServices,
47+
SlackSignatureVerifier slackSignatureVerifier,
48+
PulseSlackCommand pulseSlackCommand,
49+
SlackPulseResponseConverter slackPulseResponseConverter) {
3550
this.pulseResponseServices = pulseResponseServices;
51+
this.memberProfileServices = memberProfileServices;
52+
this.slackSignatureVerifier = slackSignatureVerifier;
53+
this.pulseSlackCommand = pulseSlackCommand;
54+
this.slackPulseResponseConverter = slackPulseResponseConverter;
3655
}
3756

3857
/**
@@ -43,6 +62,7 @@ public PulseResponseController(PulseResponseService pulseResponseServices) {
4362
* @param dateTo
4463
* @return
4564
*/
65+
@Secured(SecurityRule.IS_AUTHENTICATED)
4666
@Get("/{?teamMemberId,dateFrom,dateTo}")
4767
public Set<PulseResponse> findPulseResponses(@Nullable @Format("yyyy-MM-dd") LocalDate dateFrom,
4868
@Nullable @Format("yyyy-MM-dd") LocalDate dateTo,
@@ -56,6 +76,7 @@ public Set<PulseResponse> findPulseResponses(@Nullable @Format("yyyy-MM-dd") Loc
5676
* @param pulseResponse, {@link PulseResponseCreateDTO}
5777
* @return {@link HttpResponse<PulseResponse>}
5878
*/
79+
@Secured(SecurityRule.IS_AUTHENTICATED)
5980
@Post
6081
public HttpResponse<PulseResponse> createPulseResponse(@Body @Valid PulseResponseCreateDTO pulseResponse,
6182
HttpRequest<?> request) {
@@ -70,6 +91,7 @@ public HttpResponse<PulseResponse> createPulseResponse(@Body @Valid PulseRespons
7091
* @param pulseResponse, {@link PulseResponse}
7192
* @return {@link HttpResponse<PulseResponse>}
7293
*/
94+
@Secured(SecurityRule.IS_AUTHENTICATED)
7395
@Put
7496
public HttpResponse<PulseResponse> update(@Body @Valid @NotNull PulseResponse pulseResponse,
7597
HttpRequest<?> request) {
@@ -82,6 +104,7 @@ public HttpResponse<PulseResponse> update(@Body @Valid @NotNull PulseResponse pu
82104
* @param id
83105
* @return
84106
*/
107+
@Secured(SecurityRule.IS_AUTHENTICATED)
85108
@Get("/{id}")
86109
public PulseResponse readPulse(@NotNull UUID id) {
87110
PulseResponse result = pulseResponseServices.read(id);
@@ -90,4 +113,88 @@ public PulseResponse readPulse(@NotNull UUID id) {
90113
}
91114
return result;
92115
}
116+
117+
@Secured(SecurityRule.IS_ANONYMOUS)
118+
@Post(uri = "/command", consumes = MediaType.APPLICATION_FORM_URLENCODED)
119+
public HttpResponse commandPulseResponse(
120+
@Header("X-Slack-Signature") String signature,
121+
@Header("X-Slack-Request-Timestamp") String timestamp,
122+
@Body String requestBody) {
123+
// Validate the request
124+
if (slackSignatureVerifier.verifyRequest(signature,
125+
timestamp, requestBody)) {
126+
// Convert the request body to a map of values.
127+
FormUrlEncodedDecoder formUrlEncodedDecoder = new FormUrlEncodedDecoder();
128+
Map<String, Object> body =
129+
formUrlEncodedDecoder.decode(requestBody,
130+
StandardCharsets.UTF_8);
131+
132+
// Respond to the slack command.
133+
String triggerId = (String)body.get("trigger_id");
134+
if (pulseSlackCommand.send(triggerId)) {
135+
return HttpResponse.ok();
136+
} else {
137+
return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
138+
}
139+
} else {
140+
return HttpResponse.unauthorized();
141+
}
142+
}
143+
144+
@Secured(SecurityRule.IS_ANONYMOUS)
145+
@Post(uri = "/external", consumes = MediaType.APPLICATION_FORM_URLENCODED)
146+
public HttpResponse externalPulseResponse(
147+
@Header("X-Slack-Signature") String signature,
148+
@Header("X-Slack-Request-Timestamp") String timestamp,
149+
@Body String requestBody,
150+
HttpRequest<?> request) {
151+
// Validate the request
152+
if (slackSignatureVerifier.verifyRequest(signature,
153+
timestamp, requestBody)) {
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+
}
193+
} else {
194+
return HttpResponse.unprocessableEntity();
195+
}
196+
} else {
197+
return HttpResponse.unauthorized();
198+
}
199+
}
93200
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import jakarta.validation.constraints.NotNull;
77

88
import java.time.LocalDate;
9+
import java.util.Optional;
910
import java.util.List;
1011
import java.util.UUID;
1112

@@ -14,4 +15,5 @@ public interface PulseResponseRepository extends CrudRepository<PulseResponse, U
1415

1516
List<PulseResponse> findByTeamMemberId(@NotNull UUID teamMemberId);
1617
List<PulseResponse> findBySubmissionDateBetween(@NotNull LocalDate dateFrom, @NotNull LocalDate dateTo);
17-
}
18+
Optional<PulseResponse> getByTeamMemberIdAndSubmissionDate(@NotNull UUID teamMemberId, @NotNull LocalDate submissionDate);
19+
}

0 commit comments

Comments
 (0)