diff --git a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildController.java b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildController.java index 3aef1fdb47..f0b3f241f3 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildController.java @@ -1,9 +1,12 @@ package com.objectcomputing.checkins.services.guild; +import com.objectcomputing.checkins.services.guild.member.GuildMemberResponseDTO; +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileResponseDTO; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices; import io.micronaut.core.annotation.Nullable; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; -import io.micronaut.http.MediaType; import io.micronaut.http.annotation.*; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; @@ -15,6 +18,7 @@ import reactor.core.publisher.Mono; import java.net.URI; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; @@ -26,9 +30,11 @@ public class GuildController { private final GuildServices guildService; + private final MemberProfileServices profileServices; - public GuildController(GuildServices guildService) { + public GuildController(GuildServices guildService, MemberProfileServices profileServices) { this.guildService = guildService; + this.profileServices = profileServices; } /** @@ -57,6 +63,32 @@ public Mono> readGuild(@NotNull UUID id) { .map(HttpResponse::ok); } + /** + * Get guild leader based on guild id + * + * @param id of guild + * @return {@link GuildResponseDTO guild leader matching id} + */ + + @Get("/leaders/{id}") + public Mono>> getGuildLeaders(@NotNull UUID id) { + return Mono.fromCallable(() -> { + GuildResponseDTO guild = guildService.read(id); + List members = guild.getGuildMembers(); + Set newLeaders = new HashSet<>(); + for (GuildMemberResponseDTO member : members) { + if (member.isLead()) { + MemberProfile memberProfile = profileServices.getById(member.getMemberId()); + if (memberProfile != null) { + newLeaders.add(memberProfile); + } + } + } + return newLeaders; + }) + .map(HttpResponse::ok); + } + /** * Find guild(s) given a combination of the following parameters * diff --git a/web-ui/src/api/guild.js b/web-ui/src/api/guild.js index a09fc9cbaa..646dd56d82 100644 --- a/web-ui/src/api/guild.js +++ b/web-ui/src/api/guild.js @@ -1,7 +1,8 @@ import { resolve } from './api.js'; const guildUrl = `/services/guilds`; -const guildMemberUrl = `/services/guilds/members`; +const guildMemberUrl = `${guildUrl}/members`; +const guildLeadersUrl = `${guildUrl}/leaders`; export const getAllGuildMembers = async cookie => { return resolve({ @@ -20,6 +21,13 @@ export const getMembersByGuild = async (id, cookie) => { }); }; +export const getGuildLeaders = async (id, cookie) => { + return resolve({ + url: `${guildLeadersUrl}/${id}`, + headers: { 'X-CSRF-Header': cookie, Accept: 'application/json' } + }); +} + export const updateGuild = async (guild, cookie) => { return resolve({ method: 'PUT', diff --git a/web-ui/src/api/notifications.js b/web-ui/src/api/notifications.js index 957a27ddd4..67667cbcf2 100644 --- a/web-ui/src/api/notifications.js +++ b/web-ui/src/api/notifications.js @@ -2,7 +2,7 @@ import { resolve } from './api.js'; import { getMember } from "./member.js"; const emailNotificationURL = '/services/email-notifications'; -const emailUrl = '/services/email'; +const emailURL = '/services/email'; const testEmailURL = import.meta.env.VITE_APP_API_URL ? import.meta.env.VITE_APP_URL + '/feedback/submit?request=' : 'http://localhost:8080/feedback/submit?request='; @@ -33,7 +33,7 @@ export const sendReminderNotification = async ( export const sendEmail = async (subject, content, html, recipients, cookie) => { return resolve({ method: 'POST', - url: emailUrl, + url: emailURL, data: { subject: subject, content: content, @@ -52,13 +52,13 @@ export const emailPDLAssignment = async (member, cookie) => { if (member.pdlId && member.lastName && member.firstName && member.workEmail) { let res = await getMember(member.pdlId) let pdl = res.payload?.data && !res.error - ? res.payload.data - : null; + ? res.payload.data + : null; if (pdl?.workEmail) { await sendEmail("You have been assigned as the PDL of " + member.firstName + " " + member.lastName, - member.firstName + " " + member.lastName + - " will now report to you as their PDL. Please engage with them: " + member.workEmail, - false, [pdl.workEmail], cookie) + member.firstName + " " + member.lastName + + " will now report to you as their PDL. Please engage with them: " + member.workEmail, + false, [pdl.workEmail], cookie) } else { console.warn("Unable to send email regarding " + member.firstName + " " + member.lastName + "'s PDL update as the PDL was unable to be pulled up correctly") } @@ -70,17 +70,29 @@ export const emailSupervisorAssignment = async (member, cookie) => { if (member.supervisorid && member.lastName && member.firstName && member.workEmail) { let res = await getMember(member.supervisorid) let supervisor = res.payload?.data && !res.error - ? res.payload.data - : null; + ? res.payload.data + : null; if (supervisor?.workEmail) { await sendEmail("You have been assigned as the supervisor of " + member.firstName + " " + member.lastName, - member.firstName + " " + member.lastName + - " will now report to you as their supervisor. Please engage with them: " + member.workEmail, - false, [supervisor.workEmail], cookie) + member.firstName + " " + member.lastName + + " will now report to you as their supervisor. Please engage with them: " + member.workEmail, + false, [supervisor.workEmail], cookie) } else { console.warn("Unable to send email regarding " + member.firstName + " " + member.lastName + "'s supervisor update as the supervisor was unable to be pulled up correctly") } } else { console.warn("Unable to send email regarding as member was not valid and missing required fields", member) } +} +export const emailGuildLeaders = async (members, guild, cookie) => { + members.forEach(member => { + if (!member.workEmail || !guild?.name) { + console.warn("Unable to send guild leader email as member is missing required fields", member); + return; + } + + const subject = `You have been assigned as a guild leader of ${guild.name}`; + const body = `Congratulations, you have been assigned as a guild leader of ${guild.name}`; + sendEmail(subject, body, false, [member.workEmail], cookie); + }); } \ No newline at end of file diff --git a/web-ui/src/components/admin/users/Users.jsx b/web-ui/src/components/admin/users/Users.jsx index a6704dce9c..463d2690e6 100644 --- a/web-ui/src/components/admin/users/Users.jsx +++ b/web-ui/src/components/admin/users/Users.jsx @@ -185,11 +185,15 @@ const Users = () => { type: UPDATE_MEMBER_PROFILES, payload: [...memberProfiles, data] }); - if (member.pdlId) { - emailPDLAssignment(member, csrf).then() + try { + member.pdlId && await emailPDLAssignment(member, csrf) + } catch (e) { + console.error("Unable to send PDL assignment email", e) } - if (member.supervisorid) { - emailSupervisorAssignment(member, csrf).then() + try { + member.supervisorid && await emailSupervisorAssignment(member, csrf) + } catch (e) { + console.error("Unable to send supervisor assignment email", e) } } handleClose(); diff --git a/web-ui/src/components/guild-results/GuildResults.jsx b/web-ui/src/components/guild-results/GuildResults.jsx index 9020d26b9c..451272993b 100644 --- a/web-ui/src/components/guild-results/GuildResults.jsx +++ b/web-ui/src/components/guild-results/GuildResults.jsx @@ -1,10 +1,10 @@ import PropTypes from 'prop-types'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useState } from 'react'; import GroupIcon from '@mui/icons-material/Group'; import { Button, TextField } from '@mui/material'; import { styled } from '@mui/material/styles'; -import { createGuild } from '../../api/guild'; +import { createGuild, getGuildLeaders } from '../../api/guild'; import { ADD_GUILD } from '../../context/actions'; import { AppContext } from '../../context/AppContext'; import AddGuildModal from './EditGuildModal'; @@ -12,6 +12,7 @@ import GuildSummaryCard from './GuildSummaryCard'; import SkeletonLoader from '../skeleton_loader/SkeletonLoader'; import { useQueryParameters } from '../../helpers/query-parameters'; import './GuildResults.css'; +import { emailGuildLeaders } from "../../api/notifications.js"; const PREFIX = 'GuildResults'; const classes = { @@ -93,13 +94,21 @@ const GuildResults = () => { onClose={handleClose} onSave={async guild => { if (csrf) { - let res = await createGuild(guild, csrf); - let data = - res.payload && res.payload.data && !res.error - ? res.payload.data - : null; + const res = await createGuild(guild, csrf); + const data = res.payload?.data && !res.error + ? res.payload.data + : null; if (data) { dispatch({ type: ADD_GUILD, payload: data }); + const resGuildLeader = await getGuildLeaders(data.id, csrf); + const guildLeaders = resGuildLeader.payload?.data && !resGuildLeader.error + ? resGuildLeader.payload.data + : null; + try { + guildLeaders && await emailGuildLeaders(guildLeaders, data, csrf).then(); + } catch (e) { + console.error("Unable to email guild leader assignment(s)", e) + } } handleClose(); } @@ -113,19 +122,19 @@ const GuildResults = () => {
{guilds?.length ? guilds?.map((guild, index) => - guild.name.toLowerCase().includes(searchText.toLowerCase()) ? ( - - ) : null - ) + guild.name.toLowerCase().includes(searchText.toLowerCase()) ? ( + + ) : null + ) : Array.from({ length: 20 }).map((_, index) => ( - - ))} + + ))}
); diff --git a/web-ui/src/components/guild-results/GuildSummaryCard.jsx b/web-ui/src/components/guild-results/GuildSummaryCard.jsx index 5db7c1d96a..9344ee40e7 100644 --- a/web-ui/src/components/guild-results/GuildSummaryCard.jsx +++ b/web-ui/src/components/guild-results/GuildSummaryCard.jsx @@ -23,8 +23,9 @@ import { Tooltip } from '@mui/material'; import PropTypes from 'prop-types'; -import { deleteGuild, updateGuild } from '../../api/guild.js'; +import { deleteGuild, getGuildLeaders, updateGuild } from '../../api/guild.js'; import SplitButton from '../split-button/SplitButton'; +import { emailGuildLeaders } from "../../api/notifications.js"; const PREFIX = 'GuildSummaryCard'; const classes = { @@ -263,11 +264,14 @@ const GuildSummaryCard = ({ guild, index, isOpen, onGuildSelect }) => { open={open} onClose={handleClose} onSave={async editedGuild => { - let res = await updateGuild(editedGuild, csrf); - let data = - res.payload && res.payload.data && !res.error - ? res.payload.data - : null; + const resGetOldGuildLeader = await getGuildLeaders(editedGuild.id, csrf); + const oldGuildLeaders = resGetOldGuildLeader.payload?.data && !resGetOldGuildLeader.error + ? resGetOldGuildLeader.payload.data + : null; + const res = await updateGuild(editedGuild, csrf); + const data = res.payload?.data && !res.error + ? res.payload.data + : null; if (data) { const copy = [...guilds]; copy[index] = data; @@ -275,6 +279,21 @@ const GuildSummaryCard = ({ guild, index, isOpen, onGuildSelect }) => { type: UPDATE_GUILDS, payload: copy }); + const resGetGuildLeaders = await getGuildLeaders(editedGuild.id, csrf); + const guildLeaders = resGetGuildLeaders.payload?.data && !resGetGuildLeaders.error + ? resGetGuildLeaders.payload.data + : null; + if (guildLeaders && oldGuildLeaders) { + // Filter out the new leaders that were not in the old leaders + const newLeaders = guildLeaders.filter( + newLeader => !oldGuildLeaders.some(oldLeader => oldLeader.id === newLeader.id) + ); + try { + newLeaders.length > 0 && await emailGuildLeaders(newLeaders, guild, csrf).then(); + } catch (e) { + console.error("Unable to email guild leader assignment(s)", e) + } + } } }} headerText="Edit Your Guild" diff --git a/web-ui/src/components/member-directory/AdminMemberCard.jsx b/web-ui/src/components/member-directory/AdminMemberCard.jsx index cd32c2a505..27846fa1d6 100644 --- a/web-ui/src/components/member-directory/AdminMemberCard.jsx +++ b/web-ui/src/components/member-directory/AdminMemberCard.jsx @@ -217,8 +217,8 @@ const AdminMemberCard = ({ member, index }) => { onSave={async member => { const resGetMember = await getMember(member.id, csrf); const oldMember = resGetMember.payload?.data && !resGetMember.error - ? resGetMember.payload.data - : null; + ? resGetMember.payload.data + : null; const res = await updateMember(member, csrf); const data = res.payload?.data && !res.error @@ -234,11 +234,16 @@ const AdminMemberCard = ({ member, index }) => { type: UPDATE_MEMBER_PROFILES, payload: copy }); - if (oldMember && oldMember.pdlId !== member.pdlId) { - emailPDLAssignment(member, csrf).then() + try { + oldMember.pdlId !== member.pdlId && await emailPDLAssignment(member, csrf); + } catch (e) { + console.error("Unable to email PDL assignment", e) } - if (oldMember && oldMember.supervisorid !== member.supervisorid) { - emailSupervisorAssignment(member, csrf).then() + try { + oldMember.supervisorid !== member.supervisorid && await emailSupervisorAssignment(member, csrf); + } catch { + console.error("Unable to email supervisor assignment", e) + } handleClose(); }