diff --git a/server/src/main/java/com/objectcomputing/checkins/services/permissions/Permission.java b/server/src/main/java/com/objectcomputing/checkins/services/permissions/Permission.java
index ba203ca9a..00ddde2fb 100644
--- a/server/src/main/java/com/objectcomputing/checkins/services/permissions/Permission.java
+++ b/server/src/main/java/com/objectcomputing/checkins/services/permissions/Permission.java
@@ -17,6 +17,7 @@ public enum Permission {
CAN_ADMINISTER_FEEDBACK_ANSWER("Administer feedback answers", "Feedback"),
CAN_ADMINISTER_FEEDBACK_TEMPLATES("Administer feedback templates", "Feedback"),
CAN_SEND_EMAIL("Send email", "Feedback"),
+ CAN_VIEW_TERMINATED_MEMBERS("Can view the profiles of terminated members", "User Management"),
CAN_EDIT_ALL_ORGANIZATION_MEMBERS("Edit all member profiles", "User Management"),
CAN_DELETE_ORGANIZATION_MEMBERS("Delete organization members", "User Management"),
CAN_CREATE_ORGANIZATION_MEMBERS("Create organization members", "User Management"),
diff --git a/web-ui/src/components/kudos/PublicKudosCard.jsx b/web-ui/src/components/kudos/PublicKudosCard.jsx
index 465c10108..703a76f70 100644
--- a/web-ui/src/components/kudos/PublicKudosCard.jsx
+++ b/web-ui/src/components/kudos/PublicKudosCard.jsx
@@ -19,7 +19,10 @@ import {
TextField,
Link,
} from "@mui/material";
-import { selectCsrfToken, selectProfile } from "../../context/selectors";
+import {
+ selectCsrfToken,
+ selectActiveOrInactiveProfile,
+} from "../../context/selectors";
import { AppContext } from "../../context/AppContext";
import { getAvatarURL } from "../../api/api";
import DateFnsUtils from "@date-io/date-fns";
@@ -50,7 +53,7 @@ const KudosCard = ({ kudos }) => {
const { state, dispatch } = useContext(AppContext);
const csrf = selectCsrfToken(state);
- const sender = selectProfile(state, kudos.senderId);
+ const sender = selectActiveOrInactiveProfile(state, kudos.senderId);
const regexIndexOf = (text, regex, start) => {
const indexInSuffix = text.slice(start).search(regex);
diff --git a/web-ui/src/components/kudos_card/KudosCard.jsx b/web-ui/src/components/kudos_card/KudosCard.jsx
index 798f0d946..b856dc976 100644
--- a/web-ui/src/components/kudos_card/KudosCard.jsx
+++ b/web-ui/src/components/kudos_card/KudosCard.jsx
@@ -20,7 +20,10 @@ import {
FormControlLabel,
Checkbox,
} from "@mui/material";
-import { selectCsrfToken, selectProfile } from "../../context/selectors";
+import {
+ selectCsrfToken,
+ selectActiveOrInactiveProfile,
+} from "../../context/selectors";
import MemberSelector from '../member_selector/MemberSelector';
import { AppContext } from "../../context/AppContext";
import { getAvatarURL } from "../../api/api";
@@ -63,7 +66,7 @@ const KudosCard = ({ kudos, includeActions, includeEdit, onKudosAction }) => {
const [memberSelectorOpen, setMemberSelectorOpen] = useState(false);
const [kudosRecipientMembers, setKudosRecipientMembers] = useState(kudos.recipientMembers);
- const sender = selectProfile(state, kudos.senderId);
+ const sender = selectActiveOrInactiveProfile(state, kudos.senderId);
const getRecipientComponent = useCallback(() => {
if (kudos.recipientTeam) {
diff --git a/web-ui/src/components/kudos_card/KudosCard.test.jsx b/web-ui/src/components/kudos_card/KudosCard.test.jsx
new file mode 100644
index 000000000..f411e0441
--- /dev/null
+++ b/web-ui/src/components/kudos_card/KudosCard.test.jsx
@@ -0,0 +1,157 @@
+import React from 'react';
+import KudosCard from './KudosCard';
+import { AppContextProvider } from '../../context/AppContext';
+
+const initialState = {
+ state: {
+ csrf: 'O_3eLX2-e05qpS_yOeg1ZVAs9nDhspEi',
+ teams: [],
+ userProfile: {
+ id: "1",
+ firstName: 'Jimmy',
+ lastName: 'Johnson',
+ role: ['MEMBER'],
+ },
+ terminatedMembers: [
+ {
+ id: "5",
+ firstName: 'Jerry',
+ lastName: 'Garcia',
+ name: 'Jerry Garcia',
+ role: ['MEMBER'],
+ },
+ ],
+ memberProfiles: [
+ {
+ id: "1",
+ firstName: 'Jimmy',
+ lastName: 'Johnson',
+ name: 'Jimmy Johnson',
+ role: ['MEMBER'],
+ },
+ {
+ id: "2",
+ firstName: 'Jimmy',
+ lastName: 'Olsen',
+ name: 'Jimmy Olsen',
+ role: ['MEMBER'],
+ },
+ {
+ id: "3",
+ firstName: 'Clark',
+ lastName: 'Kent',
+ name: 'Clark Kent',
+ role: ['MEMBER'],
+ },
+ {
+ id: "4",
+ firstName: 'Kent',
+ lastName: 'Brockman',
+ name: 'Kent Brockman',
+ role: ['MEMBER'],
+ },
+ {
+ id: "6",
+ firstName: 'Brock',
+ lastName: 'Smith',
+ name: 'Brock Smith',
+ role: ['MEMBER'],
+ },
+ {
+ id: "7",
+ firstName: 'Jimmy',
+ middleName: 'T.',
+ lastName: 'Olsen',
+ name: 'Jimmy T. Olsen',
+ role: ['MEMBER'],
+ },
+ ],
+ }
+};
+
+const terminated = {
+ id: 'test-terminated-kudos',
+ message: "Brock and Brockman did a great job helping Clark, Jimmy Olsen, Jimmy T. Olsen, and Johnson",
+ senderId: "5",
+ dateCreated: [ 2025, 2, 14 ],
+ recipientMembers: [
+ {
+ id: "1",
+ firstName: 'Jimmy',
+ lastName: 'Johnson',
+ role: ['MEMBER'],
+ },
+ {
+ id: "2",
+ firstName: 'Jimmy',
+ lastName: 'Olsen',
+ role: ['MEMBER'],
+ },
+ {
+ id: "3",
+ firstName: 'Clark',
+ lastName: 'Kent',
+ role: ['MEMBER'],
+ },
+ {
+ id: "6",
+ firstName: 'Brock',
+ lastName: 'Smith',
+ role: ['MEMBER'],
+ },
+ {
+ id: "4",
+ firstName: 'Kent',
+ lastName: 'Brockman',
+ role: ['MEMBER'],
+ },
+ {
+ id: "7",
+ firstName: 'Jimmy',
+ middleName: 'T.',
+ lastName: 'Olsen',
+ role: ['MEMBER'],
+ },
+ ],
+};
+
+const kudos = {
+ id: 'test-kudos',
+ message: "Jimmy is awesome!",
+ senderId: "1",
+ dateCreated: [ 2025, 2, 17 ],
+ recipientMembers: [
+ {
+ id: "2",
+ firstName: 'Jimmy',
+ lastName: 'Olsen',
+ role: ['MEMBER'],
+ },
+ ],
+};
+
+it('inactive renders correctly', () => {
+ snapshot(
+
+ {}}
+ />
+
+ );
+});
+
+it('active renders correctly', () => {
+ snapshot(
+
+ {}}
+ />
+
+ );
+});
diff --git a/web-ui/src/components/kudos_card/__snapshots__/KudosCard.test.jsx.snap b/web-ui/src/components/kudos_card/__snapshots__/KudosCard.test.jsx.snap
new file mode 100644
index 000000000..12d66db3f
--- /dev/null
+++ b/web-ui/src/components/kudos_card/__snapshots__/KudosCard.test.jsx.snap
@@ -0,0 +1,469 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`active renders correctly 1`] = `
+
+
+
+
+
+
+
+
+
+ Jimmy is awesome!
+
+
+
+
+
+
+
+`;
+
+exports[`inactive renders correctly 1`] = `
+
+
+
+
+
+
+
+
+
+ Brock and Brockman did a great job helping Clark, Jimmy Olsen, Jimmy T. Olsen, and Johnson
+
+
+
+ Members:
+
+
+
+

+
+
+ Jimmy Johnson
+
+
+
+
+

+
+
+ Jimmy Olsen
+
+
+
+
+

+
+
+ Clark Kent
+
+
+
+
+

+
+
+ Brock Smith
+
+
+
+
+

+
+
+ Kent Brockman
+
+
+
+
+

+
+
+ Jimmy Olsen
+
+
+
+
+
+
+
+
+
+`;
diff --git a/web-ui/src/context/AppContext.jsx b/web-ui/src/context/AppContext.jsx
index 5dd175260..78481c04e 100644
--- a/web-ui/src/context/AppContext.jsx
+++ b/web-ui/src/context/AppContext.jsx
@@ -23,7 +23,7 @@ import {
} from '../api/member';
import {
selectCanViewCheckinsPermission,
- selectCanEditAllOrganizationMembers,
+ selectCanViewTerminatedMembers,
} from './selectors';
import { getAllRoles, getAllUserRoles } from '../api/roles';
import { getMemberSkills } from '../api/memberskill';
@@ -191,7 +191,7 @@ const AppContextProvider = props => {
if (csrf && userProfile && !memberProfiles) {
dispatch({ type: UPDATE_PEOPLE_LOADING, payload: true });
getMemberProfiles();
- if (selectCanEditAllOrganizationMembers(state)) {
+ if (selectCanViewTerminatedMembers(state)) {
getTerminatedMembers();
}
}
diff --git a/web-ui/src/context/selectors.js b/web-ui/src/context/selectors.js
index 984f7d43a..27d53c1c1 100644
--- a/web-ui/src/context/selectors.js
+++ b/web-ui/src/context/selectors.js
@@ -1,7 +1,7 @@
import { createSelector } from 'reselect';
export const selectMemberProfiles = state => state.memberProfiles || [];
-export const selectTerminatedMembers = state => state.terminatedMembers;
+export const selectTerminatedMembers = state => state.terminatedMembers || [];
export const selectMemberSkills = state => state.memberSkills || [];
export const selectSkills = state => state.skills || [];
export const selectTeamMembers = state => state.teamMembers;
@@ -242,6 +242,14 @@ export const selectCanEditAllOrganizationMembers = hasPermission(
'CAN_EDIT_ALL_ORGANIZATION_MEMBERS',
);
+export const selectCanViewTerminatedMembers = createSelector(
+ selectCanEditAllOrganizationMembers,
+ hasPermission(
+ 'CAN_VIEW_TERMINATED_MEMBERS'
+ ),
+ (canEdit, canView) => canEdit || canView
+);
+
export const selectIsPDL = createSelector(
selectUserProfile,
userProfile =>
@@ -327,6 +335,13 @@ export const selectProfile = createSelector(
(profileMap, profileId) => profileMap[profileId]
);
+export const selectActiveOrInactiveProfile = createSelector(
+ selectProfileMap,
+ selectProfileMapForTerminatedMembers,
+ (state, profileId) => profileId,
+ (profileMap, termedProfileMap, profileId) => profileMap[profileId] || termedProfileMap[profileId]
+);
+
export const selectSkill = createSelector(
selectSkills,
(state, skillId) => skillId,
diff --git a/web-ui/src/context/selectors.test.js b/web-ui/src/context/selectors.test.js
index 0ba8e98cd..8f6007702 100644
--- a/web-ui/src/context/selectors.test.js
+++ b/web-ui/src/context/selectors.test.js
@@ -20,7 +20,10 @@ import {
selectSupervisorHierarchyIds,
selectSubordinates,
selectIsSubordinateOfCurrentUser,
- selectHasReportPermission
+ selectHasReportPermission,
+ selectActiveOrInactiveProfile,
+ selectCanEditAllOrganizationMembers,
+ selectCanViewTerminatedMembers,
} from './selectors';
describe('Selectors', () => {
@@ -1525,4 +1528,155 @@ describe('Selectors', () => {
expect(selectHasReportPermission(testState)).toBe(false);
});
+
+ it("selectCanEditAllOrganizationMembers should return false when user does not have 'CAN_EDIT_ALL_ORGANIZATION_MEMBERS' permission", () => {
+ const testState = {
+ userProfile: {
+ firstName: 'Huey',
+ lastName: 'Emmerich',
+ role: 'MEMBER',
+ permissions: [
+ { permission: 'CAN_VIEW_FEEDBACK_REQUEST' },
+ { permission: 'CAN_VIEW_FEEDBACK_ANSWER' },
+ ]
+ }
+ };
+
+ expect(selectCanEditAllOrganizationMembers(testState)).toBe(false);
+ });
+
+ it("selectCanEditAllOrganizationMembers should return true when user has 'CAN_EDIT_ALL_ORGANIZATION_MEMBERS' permission", () => {
+ const testState = {
+ userProfile: {
+ firstName: 'Huey',
+ lastName: 'Emmerich',
+ role: 'MEMBER',
+ permissions: [
+ { permission: 'CAN_VIEW_FEEDBACK_REQUEST' },
+ { permission: 'CAN_EDIT_ALL_ORGANIZATION_MEMBERS' },
+ { permission: 'CAN_VIEW_FEEDBACK_ANSWER' },
+ ]
+ }
+ };
+
+ expect(selectCanEditAllOrganizationMembers(testState)).toBe(true);
+ });
+
+ it("selectCanViewTerminatedMembers should return false when user does not have 'CAN_EDIT_ALL_ORGANIZATION_MEMBERS' or 'CAN_VIEW_TERMINATED_MEMBERS' permission", () => {
+ const testState = {
+ userProfile: {
+ firstName: 'Huey',
+ lastName: 'Emmerich',
+ role: 'MEMBER',
+ permissions: [
+ { permission: 'CAN_VIEW_FEEDBACK_REQUEST' },
+ { permission: 'CAN_VIEW_FEEDBACK_ANSWER' },
+ ]
+ }
+ };
+
+ expect(selectCanViewTerminatedMembers(testState)).toBe(false);
+ });
+
+ it("selectCanViewTerminatedMembers should return true when user has 'CAN_EDIT_ALL_ORGANIZATION_MEMBERS' or 'CAN_VIEW_TERMINATED_MEMBERS' permissions", () => {
+ const testState = {
+ userProfile: {
+ firstName: 'Huey',
+ lastName: 'Emmerich',
+ role: 'MEMBER',
+ permissions: [
+ { permission: 'CAN_VIEW_FEEDBACK_REQUEST' },
+ { permission: 'CAN_EDIT_ALL_ORGANIZATION_MEMBERS' },
+ { permission: 'CAN_VIEW_FEEDBACK_ANSWER' },
+ ]
+ }
+ };
+ const otherTestState = {
+ userProfile: {
+ firstName: 'Huey',
+ lastName: 'Emmerich',
+ role: 'MEMBER',
+ permissions: [
+ { permission: 'CAN_VIEW_FEEDBACK_REQUEST' },
+ { permission: 'CAN_VIEW_TERMINATED_MEMBERS' },
+ { permission: 'CAN_VIEW_FEEDBACK_ANSWER' },
+ ]
+ }
+ };
+
+ expect(selectCanViewTerminatedMembers(testState)).toBe(true);
+ expect(selectCanViewTerminatedMembers(otherTestState)).toBe(true);
+ });
+
+ it('selectActiveOrInactiveProfile should a profile if active or inactive', () => {
+ const activeTestMember = {
+ id: 1,
+ bioText: 'foo',
+ employeeId: 11,
+ name: 'A Person',
+ firstName: 'A',
+ lastName: 'PersonA',
+ location: 'St Louis',
+ title: 'engineer',
+ workEmail: 'employee@sample.com',
+ pdlId: 9,
+ startDate: [2012, 9, 29],
+ };
+ const inactiveTestMember = {
+ id: 2,
+ bioText: 'foo',
+ employeeId: 12,
+ name: 'B Person',
+ firstName: 'B',
+ lastName: 'PersonB',
+ location: 'St Louis',
+ title: 'engineer',
+ workEmail: 'employee@sample.com',
+ pdlId: 9,
+ startDate: [2012, 9, 29],
+ terminationDate: [2013, 9, 29],
+ };
+ /** @type MemberProfile[] */
+ const testActiveMemberProfiles = [
+ activeTestMember,
+ {
+ id: 3,
+ bioText: 'foo',
+ employeeId: 13,
+ name: 'C Person',
+ firstName: 'C',
+ lastName: 'PersonC',
+ location: 'St Louis',
+ title: 'engineer',
+ workEmail: 'employee@sample.com',
+ pdlId: 9,
+ startDate: [2012, 9, 29],
+ }
+ ];
+ /** @type MemberProfile[] */
+ const testInactiveMemberProfiles = [
+ inactiveTestMember,
+ {
+ id: 4,
+ bioText: 'foo',
+ employeeId: 13,
+ name: 'D Person',
+ firstName: 'D',
+ lastName: 'PersonD',
+ location: 'St Louis',
+ title: 'engineer',
+ workEmail: 'employee@sample.com',
+ pdlId: 9,
+ startDate: [2012, 9, 29],
+ terminationDate: [2013, 9, 29],
+ }
+ ];
+ const testState = {
+ memberProfiles: testActiveMemberProfiles,
+ terminatedMembers: testInactiveMemberProfiles,
+ };
+
+ expect(selectActiveOrInactiveProfile(testState, activeTestMember.id)).toEqual(activeTestMember);
+ expect(selectActiveOrInactiveProfile(testState, inactiveTestMember.id)).toEqual(inactiveTestMember);
+ });
});