Skip to content

Commit

Permalink
Send an approved public kudos to the #kudos slack channel.
Browse files Browse the repository at this point in the history
  • Loading branch information
ocielliottc committed Nov 15, 2024
1 parent a41349f commit 219c330
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 1 deletion.
1 change: 1 addition & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ dependencies {
implementation("io.micrometer:context-propagation")

implementation 'ch.digitalfondue.mjml4j:mjml4j:1.0.3'
implementation("com.slack.api:slack-api-client:1.44.1")

testRuntimeOnly "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"
testRuntimeOnly "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.objectcomputing.checkins.notifications.social_media;

import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;

import jakarta.inject.Singleton;
import jakarta.inject.Inject;

import java.util.List;

@Singleton
public class SlackPoster {
@Inject
private HttpClient slackClient;

public HttpResponse post(String slackBlock) {
// See if we can have a webhook URL.
String slackWebHook = System.getenv("SLACK_WEBHOOK_URL");
if (slackWebHook != null) {
// POST it to Slack.
BlockingHttpClient client = slackClient.toBlocking();
HttpRequest<String> request = HttpRequest.POST(slackWebHook,
slackBlock);
return client.exchange(request);
}
return HttpResponse.status(HttpStatus.GONE,
"Slack Webhook URL is not configured");
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.objectcomputing.checkins.notifications.social_media;

import com.slack.api.model.block.LayoutBlock;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
import com.slack.api.model.Conversation;
import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.request.conversations.ConversationsListRequest;
import com.slack.api.methods.response.conversations.ConversationsListResponse;
import com.slack.api.methods.request.users.UsersLookupByEmailRequest;
import com.slack.api.methods.response.users.UsersLookupByEmailResponse;

import jakarta.inject.Singleton;
import jakarta.inject.Inject;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class SlackSearch {
private static final Logger LOG = LoggerFactory.getLogger(SlackSearch.class);
private static final String env = "SLACK_BOT_TOKEN";

public String findChannelId(String channelName) {
String token = System.getenv(env);
if (token != null) {
try {
MethodsClient client = Slack.getInstance().methods(token);
ConversationsListResponse response = client.conversationsList(
ConversationsListRequest.builder().build()
);

if (response.isOk()) {
for (Conversation conversation: response.getChannels()) {
if (conversation.getName().equals(channelName)) {
return conversation.getId();
}
}
}
} catch(IOException e) {
LOG.error("SlackSearch.findChannelId: " + e.toString());
} catch(SlackApiException e) {
LOG.error("SlackSearch.findChannelId: " + e.toString());
}
}
return null;
}

public String findUserId(String userEmail) {
String token = System.getenv(env);
if (token != null) {
try {
MethodsClient client = Slack.getInstance().methods(token);
UsersLookupByEmailResponse response = client.usersLookupByEmail(
UsersLookupByEmailRequest.builder().email(userEmail).build()
);

if (response.isOk()) {
return response.getUser().getId();
}
} catch(IOException e) {
LOG.error("SlackSearch.findUserId: " + e.toString());
} catch(SlackApiException e) {
LOG.error("SlackSearch.findUserId: " + e.toString());
}
}
return null;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.objectcomputing.checkins.services.kudos;

import com.objectcomputing.checkins.notifications.social_media.SlackPoster;
import com.objectcomputing.checkins.notifications.social_media.SlackSearch;
import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipientServices;
import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipient;
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
import com.objectcomputing.checkins.services.memberprofile.MemberProfileUtils;
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;

import com.slack.api.model.block.LayoutBlock;
import com.slack.api.model.block.RichTextBlock;
import com.slack.api.model.block.element.RichTextElement;
import com.slack.api.model.block.element.RichTextSectionElement;
import com.slack.api.util.json.GsonFactory;
import com.google.gson.Gson;

import java.util.UUID;
import java.util.List;
import java.util.ArrayList;

public class KudosConverter {
private record InternalBlock(
List<LayoutBlock> blocks
) {}

private final MemberProfileServices memberProfileServices;
private final KudosRecipientServices kudosRecipientServices;

public KudosConverter(MemberProfileServices memberProfileServices,
KudosRecipientServices kudosRecipientServices) {
this.memberProfileServices = memberProfileServices;
this.kudosRecipientServices = kudosRecipientServices;
}

public String toSlackBlock(Kudos kudos) {
// Build some the message text out of the Kudos data.
List<RichTextElement> content = new ArrayList<>();

// Look up the channel id from Slack
String channelName = "kudos";
SlackSearch search = new SlackSearch();
String channelId = search.findChannelId(channelName);
if (channelId == null) {
content.add(
RichTextSectionElement.Text.builder()
.text("#" + channelName)
.style(boldItalic())
.build()
);
} else {
content.add(
RichTextSectionElement.Channel.builder()
.channelId(channelId)
.style(limitedBoldItalic())
.build()
);
}
content.add(
RichTextSectionElement.Text.builder()
.text(" from ")
.style(boldItalic())
.build()
);
content.add(memberAsRichText(kudos.getSenderId()));
content.addAll(recipients(kudos));

content.add(
RichTextSectionElement.Text.builder()
.text("\n" + kudos.getMessage() + "\n")
.style(boldItalic())
.build()
);

// Bring it all together.
RichTextSectionElement element = RichTextSectionElement.builder()
.elements(content).build();
RichTextBlock richTextBlock = RichTextBlock.builder()
.elements(List.of(element)).build();
InternalBlock block = new InternalBlock(List.of(richTextBlock));
Gson mapper = GsonFactory.createSnakeCase();
return mapper.toJson(block);
}

private RichTextSectionElement.TextStyle boldItalic() {
return RichTextSectionElement.TextStyle.builder()
.bold(true).italic(true).build();
}

private RichTextSectionElement.LimitedTextStyle limitedBoldItalic() {
return RichTextSectionElement.LimitedTextStyle.builder()
.bold(true).italic(true).build();
}

private RichTextElement memberAsRichText(UUID memberId) {
// Look up the user name to get the user id from Slack
SlackSearch search = new SlackSearch();
MemberProfile profile = memberProfileServices.getById(memberId);
String userId = search.findUserId(profile.getWorkEmail());
if (userId == null) {
String name = MemberProfileUtils.getFullName(profile);
return RichTextSectionElement.Text.builder()
.text("@" + name)
.style(boldItalic())
.build();
} else {
return RichTextSectionElement.User.builder()
.userId(userId)
.style(limitedBoldItalic())
.build();
}
}

private List<RichTextElement> recipients(Kudos kudos) {
List<RichTextElement> list = new ArrayList<>();
List<KudosRecipient> recipients =
kudosRecipientServices.getAllByKudosId(kudos.getId());
String separator = " to ";
for (KudosRecipient recipient : recipients) {
list.add(RichTextSectionElement.Text.builder()
.text(separator)
.style(boldItalic())
.build());
list.add(memberAsRichText(recipient.getMemberId()));
separator = ", ";
}
return list;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.objectcomputing.checkins.configuration.CheckInsConfiguration;
import com.objectcomputing.checkins.notifications.email.EmailSender;
import com.objectcomputing.checkins.notifications.email.MailJetFactory;
import com.objectcomputing.checkins.notifications.social_media.SlackPoster;
import com.objectcomputing.checkins.exceptions.BadArgException;
import com.objectcomputing.checkins.exceptions.NotFoundException;
import com.objectcomputing.checkins.exceptions.PermissionException;
Expand All @@ -21,6 +22,9 @@
import com.objectcomputing.checkins.util.Util;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.transaction.annotation.Transactional;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;

import jakarta.inject.Named;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
Expand Down Expand Up @@ -49,6 +53,7 @@ class KudosServicesImpl implements KudosServices {
private final CheckInsConfiguration checkInsConfiguration;
private final RoleServices roleServices;
private final MemberProfileServices memberProfileServices;
private final SlackPoster slackPoster;

private enum NotificationType {
creation, approval
Expand All @@ -63,7 +68,8 @@ private enum NotificationType {
RoleServices roleServices,
MemberProfileServices memberProfileServices,
@Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender,
CheckInsConfiguration checkInsConfiguration) {
CheckInsConfiguration checkInsConfiguration,
SlackPoster slackPoster) {
this.kudosRepository = kudosRepository;
this.kudosRecipientServices = kudosRecipientServices;
this.kudosRecipientRepository = kudosRecipientRepository;
Expand All @@ -74,6 +80,7 @@ private enum NotificationType {
this.currentUserServices = currentUserServices;
this.emailSender = emailSender;
this.checkInsConfiguration = checkInsConfiguration;
this.slackPoster = slackPoster;
}

@Override
Expand Down Expand Up @@ -341,6 +348,7 @@ private void sendNotification(Kudos kudos, NotificationType notificationType) {
recipientAddresses.add(member.getWorkEmail());
}
}
slackApprovedKudos(kudos);
break;
case NotificationType.creation:
content = getAdminEmailContent(checkInsConfiguration);
Expand All @@ -366,4 +374,16 @@ private void sendNotification(Kudos kudos, NotificationType notificationType) {
LOG.error("An unexpected error occurred while sending notifications: {}", ex.getLocalizedMessage(), ex);
}
}

private void slackApprovedKudos(Kudos kudos) {
KudosConverter converter = new KudosConverter(memberProfileServices,
kudosRecipientServices);

String slackBlock = converter.toSlackBlock(kudos);
HttpResponse httpResponse =
slackPoster.post(converter.toSlackBlock(kudos));
if (httpResponse.status() != HttpStatus.OK) {
LOG.error("Unable to POST to Slack: " + httpResponse.reason());
}
}
}

0 comments on commit 219c330

Please sign in to comment.