From 07e09bd9b3c3cbe4e77df91a5eb49169585ee126 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 1 Nov 2024 14:48:57 -0500 Subject: [PATCH 01/23] Added the ability to mark guilds as inactive and filter the display based on that flag. --- .../checkins/services/guild/Guild.java | 18 ++++-- .../services/guild/GuildCreateDTO.java | 7 +- .../services/guild/GuildRepository.java | 2 +- .../services/guild/GuildResponseDTO.java | 7 +- .../services/guild/GuildServicesImpl.java | 6 +- .../services/guild/GuildUpdateDTO.java | 13 ++-- .../common/V118__alter_guild_add_active.sql | 1 + .../guild-results/EditGuildModal.jsx | 32 +++++++--- .../components/guild-results/GuildResults.jsx | 19 +++++- .../guild-results/GuildSummaryCard.jsx | 64 +------------------ 10 files changed, 81 insertions(+), 88 deletions(-) create mode 100644 server/src/main/resources/db/common/V118__alter_guild_add_active.sql diff --git a/server/src/main/java/com/objectcomputing/checkins/services/guild/Guild.java b/server/src/main/java/com/objectcomputing/checkins/services/guild/Guild.java index cb1a05dff..18e499020 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/guild/Guild.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/guild/Guild.java @@ -64,16 +64,22 @@ public class Guild { @Schema(description = "Is the guild a community") private boolean community; - public Guild(String name, @Nullable String description, @Nullable String link, boolean community) { - this(null, name, description, link, community); + @NotNull + @Column(name = "is_active") + @Schema(description = "whether the guild is active") + private boolean active = true; + + public Guild(String name, @Nullable String description, @Nullable String link, boolean community, boolean active) { + this(null, name, description, link, community, active); } - public Guild(UUID id, String name, @Nullable String description, @Nullable String link, boolean community) { + public Guild(UUID id, String name, @Nullable String description, @Nullable String link, boolean community, boolean active) { this.id = id; this.name = name; this.description = description; this.link = link; this.community = community; + this.active = active; } @Override @@ -85,13 +91,14 @@ public boolean equals(Object o) { Objects.equals(name, guild.name) && Objects.equals(description, guild.description) && Objects.equals(link, guild.link) && - Objects.equals(community, guild.community); + Objects.equals(community, guild.community) && + Objects.equals(active, guild.active); } @Override public int hashCode() { - return Objects.hash(id, name, description, link, community); + return Objects.hash(id, name, description, link, community, active); } @Override @@ -102,6 +109,7 @@ public String toString() { ", description='" + description + '\'' + ", link='" + link + ", community=" + community + + ", active=" + active + '}'; } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildCreateDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildCreateDTO.java index 79ca0d1fa..26ffdc463 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildCreateDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildCreateDTO.java @@ -36,11 +36,16 @@ public class GuildCreateDTO { @Schema(description = "Is the guild a community") private boolean community; - public GuildCreateDTO(String name, @Nullable String description, @Nullable String link, boolean community) { + @NotNull + @Schema(description = "whether the guild is active") + private boolean active; + + public GuildCreateDTO(String name, @Nullable String description, @Nullable String link, boolean community, boolean active) { this.name = name; this.description = description; this.link =link; this.community = community; + this.active = active; } @Data diff --git a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildRepository.java b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildRepository.java index f914952e9..c21676cbd 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildRepository.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildRepository.java @@ -29,7 +29,7 @@ public interface GuildRepository extends CrudRepository { @Query(value = "SELECT t_.id, PGP_SYM_DECRYPT(cast(t_.name as bytea),'${aes.key}') as name, " + "PGP_SYM_DECRYPT(cast(description as bytea),'${aes.key}') as description, " + "PGP_SYM_DECRYPT(cast(link as bytea),'${aes.key}') as link, " + - "t_.community as community " + + "t_.community as community, is_active " + "FROM guild t_ " + "LEFT JOIN guild_member tm_ " + " ON t_.id = tm_.guildid " + diff --git a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildResponseDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildResponseDTO.java index d982ffae4..423d0aff0 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildResponseDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildResponseDTO.java @@ -40,12 +40,17 @@ public class GuildResponseDTO { @Schema(description = "Is the guild a community") private boolean community; - public GuildResponseDTO(UUID id, String name, @Nullable String description, @Nullable String link, boolean community) { + @NotNull + @Schema(description = "whether the guild is active") + private boolean active; + + public GuildResponseDTO(UUID id, String name, @Nullable String description, @Nullable String link, boolean community, boolean active) { this.id = id; this.name = name; this.description = description; this.link = link; this.community = community; + this.active = active; } public List getGuildMembers() { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildServicesImpl.java index 388eed72a..7408414e0 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildServicesImpl.java @@ -252,7 +252,7 @@ private Guild fromDTO(GuildUpdateDTO dto) { if (dto == null) { return null; } - return new Guild(dto.getId(), dto.getName(), dto.getDescription(), dto.getLink(), dto.isCommunity()); + return new Guild(dto.getId(), dto.getName(), dto.getDescription(), dto.getLink(), dto.isCommunity(), dto.isActive()); } private GuildMember fromMemberDTO(GuildCreateDTO.GuildMemberCreateDTO memberDTO, UUID guildId) { @@ -276,7 +276,7 @@ private GuildResponseDTO fromEntity(Guild entity, List m return null; } GuildResponseDTO dto = new GuildResponseDTO(entity.getId(), entity.getName(), entity.getDescription(), - entity.getLink(), entity.isCommunity()); + entity.getLink(), entity.isCommunity(), entity.isActive()); dto.setGuildMembers(memberEntities); return dto; } @@ -285,7 +285,7 @@ private Guild fromDTO(GuildCreateDTO dto) { if (dto == null) { return null; } - return new Guild(null, dto.getName(), dto.getDescription(), dto.getLink(), dto.isCommunity()); + return new Guild(null, dto.getName(), dto.getDescription(), dto.getLink(), dto.isCommunity(), dto.isActive()); } private GuildMemberResponseDTO fromMemberEntity(GuildMember guildMember, MemberProfile memberProfile) { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildUpdateDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildUpdateDTO.java index 2448b6c20..efd4dcda4 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildUpdateDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/guild/GuildUpdateDTO.java @@ -37,16 +37,21 @@ public class GuildUpdateDTO { @Schema(description = "Is the guild a community") private boolean community; - public GuildUpdateDTO(UUID id, String name, @Nullable String description, @Nullable String link, boolean community) { + @NotNull + @Schema(description = "whether the guild is active") + private boolean active; + + public GuildUpdateDTO(UUID id, String name, @Nullable String description, @Nullable String link, boolean community, boolean active) { this.id = id; this.name = name; this.description = description; this.link = link; this.community = community; + this.active = active; } - public GuildUpdateDTO(String id, String name, String description, @Nullable String link, boolean community) { - this(nullSafeUUIDFromString(id), name, description, link, community); + public GuildUpdateDTO(String id, String name, String description, @Nullable String link, boolean community, boolean active) { + this(nullSafeUUIDFromString(id), name, description, link, community, active); } public GuildUpdateDTO() { @@ -75,4 +80,4 @@ public GuildMemberUpdateDTO(UUID id, UUID memberId, Boolean lead) { this.lead = lead; } } -} \ No newline at end of file +} diff --git a/server/src/main/resources/db/common/V118__alter_guild_add_active.sql b/server/src/main/resources/db/common/V118__alter_guild_add_active.sql new file mode 100644 index 000000000..2d1b72282 --- /dev/null +++ b/server/src/main/resources/db/common/V118__alter_guild_add_active.sql @@ -0,0 +1 @@ +ALTER TABLE guild ADD COLUMN is_active BOOLEAN DEFAULT TRUE; diff --git a/web-ui/src/components/guild-results/EditGuildModal.jsx b/web-ui/src/components/guild-results/EditGuildModal.jsx index 41e009a2e..9e8ecebec 100644 --- a/web-ui/src/components/guild-results/EditGuildModal.jsx +++ b/web-ui/src/components/guild-results/EditGuildModal.jsx @@ -156,15 +156,29 @@ const EditGuildModal = ({ guild = {}, open, onSave, onClose, headerText }) => {

{headerText}

- setGuild({ ...editedGuild, name: e.target.value })} - /> +
+ setGuild({ ...editedGuild, name: e.target.value })} + /> + {guild.id && { + const { checked } = event.target; + setGuild({ ...editedGuild, active: checked }); + }} + /> + } + label="Active" + />} +
{ const [addOpen, setAddOpen] = useState(false); const [openedGuildId, setOpenedGuildId] = useState(''); const [searchText, setSearchText] = useState(''); + const [activeGuilds, setActiveGuilds] = useState(true); useQueryParameters([ { @@ -93,6 +94,7 @@ const GuildResults = () => { onClose={handleClose} onSave={async guild => { if (csrf) { + guild.active = true; const res = await createGuild(guild, csrf); const data = res.payload?.data && !res.error ? res.payload.data : null; @@ -110,12 +112,25 @@ const GuildResults = () => { /> )} + { + const { checked } = event.target; + setActiveGuilds(checked); + }} + /> + } + label="Active Guilds Only" + />
{guilds ? guilds.map((guild, index) => - guild.name.toLowerCase().includes(searchText.toLowerCase()) ? ( + guild.name.toLowerCase().includes(searchText.toLowerCase()) && + ((activeGuilds && guild.active) || !activeGuilds) ? ( { const { state, dispatch } = useContext(AppContext); const { guilds, userProfile, csrf } = state; const [open, setOpen] = useState(isOpen); - const [openDelete, setOpenDelete] = useState(false); const [tooltipIsOpen, setTooltipIsOpen] = useState(false); const isAdmin = userProfile && userProfile.role && userProfile.role.includes('ADMIN'); @@ -97,43 +95,6 @@ const GuildSummaryCard = ({ guild, index, isOpen, onGuildSelect }) => { onGuildSelect(''); }; - const handleOpenDeleteConfirmation = () => setOpenDelete(true); - const handleCloseDeleteConfirmation = () => setOpenDelete(false); - - const guildId = guild?.id; - const deleteAGuild = useCallback(async () => { - if (guildId && csrf) { - const result = await deleteGuild(guildId, csrf); - if (result && result.payload && result.payload.status === 200) { - window.snackDispatch({ - type: UPDATE_TOAST, - payload: { - severity: 'success', - toast: 'Guild deleted' - } - }); - let newGuilds = guilds.filter(guild => { - return guild.id !== guildId; - }); - dispatch({ - type: UPDATE_GUILDS, - payload: newGuilds - }); - } - } - }, [guildId, csrf, dispatch, guilds]); - - const options = - isAdmin || isGuildLead ? ['Edit Guild', 'Delete Guild'] : ['Edit Guild']; - - const handleAction = (e, index) => { - if (index === 0) { - handleOpen(); - } else if (index === 1) { - handleOpenDeleteConfirmation(); - } - }; - const iconStyles = { position: 'absolute', bottom: '0.5rem', @@ -232,28 +193,7 @@ const GuildSummaryCard = ({ guild, index, isOpen, onGuildSelect }) => { {(isAdmin || isGuildLead) && ( <> - - - Delete guild? - - - Are you sure you want to delete the guild? - - - - - - - + )} From 4aed8c98d97918b8448871120b183538a190185d Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 1 Nov 2024 15:01:10 -0500 Subject: [PATCH 02/23] Updated to reflect the new layout. --- .../__snapshots__/GuildResults.test.jsx.snap | 255 +++++------------- .../GuildSummaryCard.test.jsx.snap | 100 ++----- .../__snapshots__/GuildsPage.test.jsx.snap | 34 ++- 3 files changed, 114 insertions(+), 275 deletions(-) diff --git a/web-ui/src/components/guild-results/__snapshots__/GuildResults.test.jsx.snap b/web-ui/src/components/guild-results/__snapshots__/GuildResults.test.jsx.snap index aff2f74be..392942f8a 100644 --- a/web-ui/src/components/guild-results/__snapshots__/GuildResults.test.jsx.snap +++ b/web-ui/src/components/guild-results/__snapshots__/GuildResults.test.jsx.snap @@ -72,206 +72,42 @@ exports[`renders correctly 1`] = ` class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root" /> -
- -
-
-
-
- string + + + -
- string -
-
-
-
-
- - Guild Leads: - - None Assigned -
- - Guild Members: - - None Assigned -
-
-
-
- - -
-
-
- -
-
-
-
+ + - - stuff - - -
- -
-
-
- - Guild Leads: - - None Assigned -
- - Guild Members: - - None Assigned -
-
-
-
- - -
-
-
- + Active Guilds Only + +
+
`; @@ -290,8 +126,8 @@ exports[`renders correctly when no guilds are loaded 1`] = ` @@ -301,7 +137,7 @@ exports[`renders correctly when no guilds are loaded 1`] = ` +
-
-
- - -
-
+
-
- - -
-
+
+ > + +
Date: Fri, 1 Nov 2024 15:20:26 -0500 Subject: [PATCH 03/23] Updated to provide the "active" flag for guilds. --- .../checkins/services/fixture/GuildFixture.java | 12 ++++++------ .../services/guild/GuildControllerTest.java | 4 ++-- .../checkins/services/guild/GuildTest.java | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/server/src/test/java/com/objectcomputing/checkins/services/fixture/GuildFixture.java b/server/src/test/java/com/objectcomputing/checkins/services/fixture/GuildFixture.java index 80c191dbf..efcce7dd1 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/fixture/GuildFixture.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/fixture/GuildFixture.java @@ -12,27 +12,27 @@ public interface GuildFixture extends MemberProfileFixture, RepositoryFixture{ String COMPASS_ADDRESS = "https://www.compass.objectcomputing.com/"; default Guild createDefaultGuild() { - return getGuildRepository().save(new Guild(UUID.randomUUID(), "Ninja", "Warriors", COMPASS_ADDRESS+"ninja_warriors/", false)); + return getGuildRepository().save(new Guild(UUID.randomUUID(), "Ninja", "Warriors", COMPASS_ADDRESS+"ninja_warriors/", false, true)); } default Guild createAnotherDefaultGuild() { - return getGuildRepository().save(new Guild(UUID.randomUUID(), "Coding", "Warriors", COMPASS_ADDRESS+"coding_warriors/", false)); + return getGuildRepository().save(new Guild(UUID.randomUUID(), "Coding", "Warriors", COMPASS_ADDRESS+"coding_warriors/", false, true)); } default GuildCreateDTO createFromEntity(Guild entity) { - return new GuildCreateDTO(entity.getName(), entity.getDescription(), entity.getLink(), false); + return new GuildCreateDTO(entity.getName(), entity.getDescription(), entity.getLink(), false, true); } default GuildUpdateDTO updateFromEntity(Guild entity) { - return new GuildUpdateDTO(entity.getId(), entity.getName(), entity.getDescription(),entity.getLink(), entity.isCommunity()); + return new GuildUpdateDTO(entity.getId(), entity.getName(), entity.getDescription(),entity.getLink(), entity.isCommunity(), entity.isActive()); } default GuildResponseDTO responseFromEntity(Guild entity) { - return new GuildResponseDTO(entity.getId(), entity.getName(), entity.getDescription(),entity.getLink(), entity.isCommunity()); + return new GuildResponseDTO(entity.getId(), entity.getName(), entity.getDescription(),entity.getLink(), entity.isCommunity(), entity.isActive()); } default Guild entityFromDTO(GuildUpdateDTO dto) { - return new Guild(dto.getId(), dto.getName(), dto.getDescription(),dto.getLink(), dto.isCommunity()); + return new Guild(dto.getId(), dto.getName(), dto.getDescription(),dto.getLink(), dto.isCommunity(), dto.isActive()); } } diff --git a/server/src/test/java/com/objectcomputing/checkins/services/guild/GuildControllerTest.java b/server/src/test/java/com/objectcomputing/checkins/services/guild/GuildControllerTest.java index dd31663dd..3b793a2bc 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/guild/GuildControllerTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/guild/GuildControllerTest.java @@ -399,7 +399,7 @@ void testUpdateGuildWithExistingMembers() { void testUpdateGuildNullName() { Guild guildEntity = createDefaultGuild(); - GuildUpdateDTO requestBody = new GuildUpdateDTO(guildEntity.getId(), null, null, null, false); + GuildUpdateDTO requestBody = new GuildUpdateDTO(guildEntity.getId(), null, null, null, false, true); requestBody.setGuildMembers(new ArrayList<>()); final HttpRequest request = HttpRequest.PUT("", requestBody) @@ -434,7 +434,7 @@ void testUpdateGuildNotExist() { Guild guildEntity = createDefaultGuild(); UUID requestId = UUID.randomUUID(); GuildUpdateDTO requestBody = new GuildUpdateDTO(requestId.toString(), guildEntity.getName(), - guildEntity.getDescription(), guildEntity.getLink(), guildEntity.isCommunity()); + guildEntity.getDescription(), guildEntity.getLink(), guildEntity.isCommunity(), guildEntity.isActive()); requestBody.setGuildMembers(new ArrayList<>()); MemberProfile memberProfileOfAdmin = createAnUnrelatedUser(); diff --git a/server/src/test/java/com/objectcomputing/checkins/services/guild/GuildTest.java b/server/src/test/java/com/objectcomputing/checkins/services/guild/GuildTest.java index 961a04907..4a0e27b4b 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/guild/GuildTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/guild/GuildTest.java @@ -101,7 +101,7 @@ void setUp() { void testGuildInstantiation() { final String name = "name"; final String description = "description"; - Guild guild = new Guild(name, description, null, false); + Guild guild = new Guild(name, description, null, false, true); assertEquals(guild.getName(), name); assertEquals(guild.getDescription(), description); } @@ -112,7 +112,7 @@ void testGuildInstantiation2() { final String name = "name"; final String description = "description"; final String link = "https://www.compass.objectcomputing.com/guilds/name"; - Guild guild = new Guild(id, name, description, link, false); + Guild guild = new Guild(id, name, description, link, false, true); assertEquals(guild.getId(), id); assertEquals(guild.getName(), name); assertEquals(guild.getDescription(), description); @@ -126,7 +126,7 @@ void testConstraintViolation() { final UUID id = UUID.randomUUID(); final String name = "name"; final String description = "description"; - Guild guild = new Guild(id, name, description, null, false); + Guild guild = new Guild(id, name, description, null, false, true); guild.setName(""); @@ -143,8 +143,8 @@ void testEquals() { final String name = "name"; final String description = "description"; final String link = "https://www.compass.objectcomputing.com/guilds/name"; - Guild g = new Guild(id, name, description, link, false); - Guild g2 = new Guild(id, name, description, link, false); + Guild g = new Guild(id, name, description, link, false, true); + Guild g2 = new Guild(id, name, description, link, false, true); assertEquals(g, g2); @@ -160,7 +160,7 @@ void testHash() { final String name = "name"; final String description = "description"; final String link = "https://www.compass.objectcomputing.com/guilds/name"; - Guild g = new Guild(id, name, description, link, false); + Guild g = new Guild(id, name, description, link, false, true); map.put(g, true); @@ -174,7 +174,7 @@ void testToString() { final String description = "description------description"; final String link = "https://www.compass.objectcomputing.com/guilds/name"; final String isCommunity = "false"; - Guild g = new Guild(id, name, description,link, false); + Guild g = new Guild(id, name, description,link, false, true); String s = g.toString(); assertTrue(s.contains(name)); @@ -261,7 +261,7 @@ void testSaveGuildWithValidData() { MemberProfile memberProfile = new MemberProfile(); memberProfile.setWorkEmail("test@example.com"); - Guild guild = new Guild(UUID.randomUUID(), "test", "example", link, true); + Guild guild = new Guild(UUID.randomUUID(), "test", "example", link, true, true); when(guildsRepo.search(any(), any())).thenReturn(Collections.emptyList()); when(guildsRepo.save(any())).thenReturn(guild); when(memberProfileServices.getById(any())).thenReturn(memberProfile); From 610805bc169b136eea3a9f1c9ce52eda166b6567 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 4 Nov 2024 08:54:17 -0600 Subject: [PATCH 04/23] Added text to the guild card indicating whether or not it is inactive. --- .../guild-results/GuildSummaryCard.jsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/web-ui/src/components/guild-results/GuildSummaryCard.jsx b/web-ui/src/components/guild-results/GuildSummaryCard.jsx index cef0ebc1f..5b59c657c 100644 --- a/web-ui/src/components/guild-results/GuildSummaryCard.jsx +++ b/web-ui/src/components/guild-results/GuildSummaryCard.jsx @@ -20,7 +20,8 @@ import { DialogContent, DialogActions, Link as StyledLink, - Tooltip + Tooltip, + Typography, } from '@mui/material'; import PropTypes from 'prop-types'; import { updateGuild } from '../../api/guild.js'; @@ -54,6 +55,11 @@ const StyledCard = styled(Card)(() => ({ } })); +const inactiveStyle = { + 'color': 'var(--action-disabled)', + 'font-size': '0.75em', +}; + const propTypes = { guild: PropTypes.shape({ id: PropTypes.string, @@ -139,6 +145,14 @@ const GuildSummaryCard = ({ guild, index, isOpen, onGuildSelect }) => { } /> + {!guild.active && ( + + Inactive + + )} {guild?.link ? (
From 22b62de819a37db7cc78cfb31980c7a8e9d62483 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 4 Nov 2024 09:14:07 -0600 Subject: [PATCH 05/23] Updated the test to show both active and inactive guilds. --- .../components/guild-results/GuildSummaryCard.test.jsx | 3 ++- .../__snapshots__/GuildSummaryCard.test.jsx.snap | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/web-ui/src/components/guild-results/GuildSummaryCard.test.jsx b/web-ui/src/components/guild-results/GuildSummaryCard.test.jsx index 18e058d30..3d5f6a41e 100644 --- a/web-ui/src/components/guild-results/GuildSummaryCard.test.jsx +++ b/web-ui/src/components/guild-results/GuildSummaryCard.test.jsx @@ -19,7 +19,8 @@ const guilds = [ name: 'testname', lead: true } - ] + ], + active: true, } ]; diff --git a/web-ui/src/components/guild-results/__snapshots__/GuildSummaryCard.test.jsx.snap b/web-ui/src/components/guild-results/__snapshots__/GuildSummaryCard.test.jsx.snap index 5ac2a399e..e2c7f138e 100644 --- a/web-ui/src/components/guild-results/__snapshots__/GuildSummaryCard.test.jsx.snap +++ b/web-ui/src/components/guild-results/__snapshots__/GuildSummaryCard.test.jsx.snap @@ -32,6 +32,11 @@ exports[`renders correctly 1`] = `
+

+ Inactive +

Guild Leads: @@ -94,6 +99,11 @@ exports[`renders correctly for ADMIN 1`] = `
+

+ Inactive +

Guild Leads: From d538fba8b56c6e5cf36bf40397af23784c0f8a30 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 5 Nov 2024 09:38:04 -0600 Subject: [PATCH 06/23] Use Mui Grid for the anniversaries, birthdays, and kudos to allow the columns to rearrange themselves. --- web-ui/src/pages/HomePage.jsx | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/web-ui/src/pages/HomePage.jsx b/web-ui/src/pages/HomePage.jsx index 9c2946ba2..fc799b66d 100644 --- a/web-ui/src/pages/HomePage.jsx +++ b/web-ui/src/pages/HomePage.jsx @@ -9,7 +9,7 @@ import MyBirthday from '../components/celebrations/MyBirthday'; import { AppContext } from '../context/AppContext'; import { selectCsrfToken, selectCurrentUser } from '../context/selectors'; import { sortAnniversaries, sortBirthdays } from '../context/util'; -import { Button } from '@mui/material'; +import { Button, Grid } from '@mui/material'; import './HomePage.css'; @@ -76,6 +76,13 @@ export default function HomePage() { return document.cookie.indexOf("OJWT=") != -1; } + // This width matches the birthdays-card and anniversaries-card style. + // However, we do not want to set this width on the PublicKudos css as it is + // used elsewhere and does not need to have it's width restricted. This only + // applies if if we have birthdays or anniversaries to display on this page. + const kudosStyle = birthdays.length == 0 && + anniversaries.length == 0 ? {} : { width: '450px' }; + return (
@@ -87,11 +94,19 @@ export default function HomePage() { myAnniversary={myAnniversaryData} /> ) : ( - <> - { anniversaries.length > 0 && () } - { birthdays.length > 0 && () } - - + + { anniversaries.length > 0 && ( + + + ) } + { birthdays.length > 0 && ( + + + ) } + + + + )}
{checkForImpersonation() && From 1d74bb92fae4dfaaf44989022f9cf2c47a073cb7 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 5 Nov 2024 09:46:04 -0600 Subject: [PATCH 07/23] Updated snapshot. --- .../__snapshots__/HomePage.test.jsx.snap | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/web-ui/src/pages/__snapshots__/HomePage.test.jsx.snap b/web-ui/src/pages/__snapshots__/HomePage.test.jsx.snap index 2e19b9684..a69831db7 100644 --- a/web-ui/src/pages/__snapshots__/HomePage.test.jsx.snap +++ b/web-ui/src/pages/__snapshots__/HomePage.test.jsx.snap @@ -9,49 +9,58 @@ exports[`renders correctly 1`] = ` class="celebrations" >
-

- Kudos -

- -
-
-
+ +
+ class="MuiGrid-root MuiGrid-container MuiGrid-spacing-xs-3 css-zow5z4-MuiGrid-root" + > +
+
+
+
From 4e1f1ff83f3d6eade09291c4270f3a92f0e54d62 Mon Sep 17 00:00:00 2001 From: Michael Kimberlin Date: Tue, 5 Nov 2024 12:45:36 -0600 Subject: [PATCH 08/23] Changed app context logic to get CSRF value and reuse it if available --- web-ui/src/context/AppContext.jsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/web-ui/src/context/AppContext.jsx b/web-ui/src/context/AppContext.jsx index 32f837881..45c213d32 100644 --- a/web-ui/src/context/AppContext.jsx +++ b/web-ui/src/context/AppContext.jsx @@ -34,6 +34,17 @@ import {getCertifications} from "../api/certification.js"; const AppContext = React.createContext(); +function getSessionCookieValue(name) { + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim(); + if (cookie.startsWith(name + '=')) { + return decodeURIComponent(cookie.substring(name.length + 1)); + } + } + return null; +} + const AppContextProvider = props => { const [state, dispatch] = useReducer( reducer, @@ -63,7 +74,8 @@ const AppContextProvider = props => { const url = `${BASE_API_URL}/csrf/cookie`; useEffect(() => { const getCsrf = async () => { - if (!csrf) { + const cookieVal = getSessionCookieValue('_csrf'); + if (!csrf && !cookieVal) { const res = await fetch(url, { responseType: 'text', credentials: 'include' @@ -71,6 +83,8 @@ const AppContextProvider = props => { if (res && res.ok) { dispatch({ type: SET_CSRF, payload: await res.text() }); } + } else if (cookieVal) { + dispatch({ type: SET_CSRF, payload: cookieVal }); } }; getCsrf(); From e8091fd01ae71fe5cdaea87d73ed9c63456cf07e Mon Sep 17 00:00:00 2001 From: Michael Kimberlin Date: Tue, 5 Nov 2024 12:57:29 -0600 Subject: [PATCH 09/23] Avoided undefined document --- web-ui/src/context/AppContext.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-ui/src/context/AppContext.jsx b/web-ui/src/context/AppContext.jsx index 45c213d32..0bd2819d0 100644 --- a/web-ui/src/context/AppContext.jsx +++ b/web-ui/src/context/AppContext.jsx @@ -35,7 +35,7 @@ import {getCertifications} from "../api/certification.js"; const AppContext = React.createContext(); function getSessionCookieValue(name) { - const cookies = document.cookie.split(';'); + const cookies = document?.cookie?.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.startsWith(name + '=')) { From 9e96e6a348eacfdca65ea9eca1a5df1f30b4e366 Mon Sep 17 00:00:00 2001 From: Michael Kimberlin Date: Tue, 5 Nov 2024 13:18:14 -0600 Subject: [PATCH 10/23] Switched to vi.useFakeTimers() from spy --- web-ui/src/pages/AnniversaryReportPage.test.jsx | 10 ++++++---- web-ui/src/pages/BirthdayReportPage.test.jsx | 11 +++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/web-ui/src/pages/AnniversaryReportPage.test.jsx b/web-ui/src/pages/AnniversaryReportPage.test.jsx index 3258d10aa..15a2448af 100644 --- a/web-ui/src/pages/AnniversaryReportPage.test.jsx +++ b/web-ui/src/pages/AnniversaryReportPage.test.jsx @@ -14,7 +14,8 @@ const userStateWithPermission = { it('renders correctly', () => { const mockDate = new Date(2022, 1, 1); - const spy = vi.spyOn(global, 'Date').mockImplementation(() => mockDate); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); snapshot( @@ -22,12 +23,13 @@ it('renders correctly', () => { ); - spy.mockRestore(); + vi.useRealTimers(); }); it('renders an error if user does not have appropriate permission', () => { const mockDate = new Date(2022, 1, 1); - const spy = vi.spyOn(global, 'Date').mockImplementation(() => mockDate); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); snapshot( @@ -35,5 +37,5 @@ it('renders an error if user does not have appropriate permission', () => { ); - spy.mockRestore(); + vi.useRealTimers(); }); diff --git a/web-ui/src/pages/BirthdayReportPage.test.jsx b/web-ui/src/pages/BirthdayReportPage.test.jsx index fe744817f..f70cfbdc0 100644 --- a/web-ui/src/pages/BirthdayReportPage.test.jsx +++ b/web-ui/src/pages/BirthdayReportPage.test.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { vi } from 'vitest'; import BirthdayReportPage from './BirthdayReportPage'; import { AppContextProvider } from '../context/AppContext'; @@ -14,7 +15,8 @@ const userStateWithPermission = { it('renders correctly', () => { const mockDate = new Date(2022, 1, 1); - const spy = vi.spyOn(global, 'Date').mockImplementation(() => mockDate); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); snapshot( @@ -22,12 +24,13 @@ it('renders correctly', () => { ); - spy.mockRestore(); + vi.useRealTimers(); }); it('renders an error if user does not have appropriate permission', () => { const mockDate = new Date(2022, 1, 1); - const spy = vi.spyOn(global, 'Date').mockImplementation(() => mockDate); + vi.useFakeTimers(); + vi.setSystemTime(mockDate); snapshot( @@ -35,5 +38,5 @@ it('renders an error if user does not have appropriate permission', () => { ); - spy.mockRestore(); + vi.useRealTimers(); }); From 6d0084c0a2758a968de1dccb0a74c806aaa42db8 Mon Sep 17 00:00:00 2001 From: Michael Kimberlin Date: Tue, 5 Nov 2024 13:25:15 -0600 Subject: [PATCH 11/23] Adjusted logic of csrf setting --- web-ui/src/context/AppContext.jsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/web-ui/src/context/AppContext.jsx b/web-ui/src/context/AppContext.jsx index 0bd2819d0..bd08fc7dc 100644 --- a/web-ui/src/context/AppContext.jsx +++ b/web-ui/src/context/AppContext.jsx @@ -74,17 +74,19 @@ const AppContextProvider = props => { const url = `${BASE_API_URL}/csrf/cookie`; useEffect(() => { const getCsrf = async () => { - const cookieVal = getSessionCookieValue('_csrf'); - if (!csrf && !cookieVal) { - const res = await fetch(url, { - responseType: 'text', - credentials: 'include' - }); - if (res && res.ok) { - dispatch({ type: SET_CSRF, payload: await res.text() }); + if (!csrf) { + const payload = getSessionCookieValue('_csrf'); + if (payload) { + dispatch({ type: SET_CSRF, payload }); + } else { + const res = await fetch(url, { + responseType: 'text', + credentials: 'include' + }); + if (res && res.ok) { + dispatch({ type: SET_CSRF, payload: await res.text() }); + } } - } else if (cookieVal) { - dispatch({ type: SET_CSRF, payload: cookieVal }); } }; getCsrf(); From 4cbc8b997b54322d6dc07aea9ab09e6090a62894 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 5 Nov 2024 14:21:27 -0600 Subject: [PATCH 12/23] Use the current date for the canned review periods to make it quicker to use them during manual testing. --- server/src/main/resources/db/dev/R__Load_testing_data.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/resources/db/dev/R__Load_testing_data.sql b/server/src/main/resources/db/dev/R__Load_testing_data.sql index 2d2aa68d3..4693bf5fd 100644 --- a/server/src/main/resources/db/dev/R__Load_testing_data.sql +++ b/server/src/main/resources/db/dev/R__Load_testing_data.sql @@ -1291,12 +1291,12 @@ VALUES INSERT INTO review_periods (id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) VALUES - ('12345678-e29c-4cf4-9ea4-6baa09405c57', 'Review Period 1', 'PLANNING', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-01 06:00:00', '2024-09-02 06:00:00', '2024-09-03 06:00:00', '2024-01-01 06:00:00', '2024-08-31 06:00:00'); + ('12345678-e29c-4cf4-9ea4-6baa09405c57', 'Review Period 1', 'PLANNING', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', CURRENT_DATE + TIME '06:00:00', CURRENT_DATE + INTERVAL '1' DAY + TIME '06:00:00', CURRENT_DATE + INTERVAL '2' DAY + TIME '06:00:00', date_trunc('year', CURRENT_DATE) + TIME '06:00:00', CURRENT_DATE + INTERVAL '-1' DAY + TIME '06:00:00'); INSERT INTO review_periods (id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) VALUES - ('12345678-e29c-4cf4-9ea4-6baa09405c58', 'Review Period 2', 'PLANNING', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-10 06:00:00', '2024-09-11 06:00:00', '2024-09-12 06:00:00', '2024-01-01 06:00:00', '2024-08-31 06:00:00'); + ('12345678-e29c-4cf4-9ea4-6baa09405c58', 'Review Period 2', 'PLANNING', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', CURRENT_DATE + TIME '06:00:00', CURRENT_DATE + INTERVAL '1' DAY + TIME '06:00:00', CURRENT_DATE + INTERVAL '2' DAY + TIME '06:00:00', date_trunc('year', CURRENT_DATE) + TIME '06:00:00', CURRENT_DATE + INTERVAL '-1' DAY + TIME '06:00:00'); INSERT INTO review_periods (id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) From a148613ef4c7ee8cf62bf5968defc32d7e386fe3 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 5 Nov 2024 14:22:10 -0600 Subject: [PATCH 13/23] Fixed the sorting of review periods to make it quicker to find the open review periods. --- web-ui/src/components/reviews/periods/ReviewPeriods.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web-ui/src/components/reviews/periods/ReviewPeriods.jsx b/web-ui/src/components/reviews/periods/ReviewPeriods.jsx index 93bec7da3..4d30109ce 100644 --- a/web-ui/src/components/reviews/periods/ReviewPeriods.jsx +++ b/web-ui/src/components/reviews/periods/ReviewPeriods.jsx @@ -389,11 +389,14 @@ const ReviewPeriods = ({ onPeriodSelected, mode }) => { ) : periods.length > 0 ? ( periods .sort((a, b) => { + const aName = (a.name || ''); return a.reviewStatus === b.reviewStatus - ? (a.name || '').localeCompare(b.name) + ? aName.localeCompare(b.name) : a.reviewStatus === ReviewStatus.OPEN ? -1 - : 1; + : (b.reviewStatus === ReviewStatus.OPEN + ? 1 + : aName.localeCompare(b.name)); }) .map((period) => (
From 1fb363aa0078634f2bf6415ebdcd032266b5484c Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 5 Nov 2024 14:23:15 -0600 Subject: [PATCH 14/23] Disable submission and editing once the user has clicked the submit button. --- .../feedback_submit_form/FeedbackSubmitForm.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/web-ui/src/components/feedback_submit_form/FeedbackSubmitForm.jsx b/web-ui/src/components/feedback_submit_form/FeedbackSubmitForm.jsx index df169e48a..0cce738fa 100644 --- a/web-ui/src/components/feedback_submit_form/FeedbackSubmitForm.jsx +++ b/web-ui/src/components/feedback_submit_form/FeedbackSubmitForm.jsx @@ -78,6 +78,7 @@ const FeedbackSubmitForm = ({ const { state, dispatch } = useContext(AppContext); const csrf = selectCsrfToken(state); const [isLoading, setIsLoading] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const [isReviewing, setIsReviewing] = useState(reviewOnly); const history = useHistory(); const [questionAnswerPairs, setQuestionAnswerPairs] = useState([]); @@ -117,6 +118,7 @@ const FeedbackSubmitForm = ({ } const onSubmitHandler = () => { + setIsSubmitting(true); updateAllAnswersSubmit() .then(res => { for (let i = 0; i < res.length; ++i) { @@ -135,9 +137,11 @@ const FeedbackSubmitForm = ({ }) .then(resTwo => { if (resTwo === false) { + setIsSubmitting(false); return; } updateRequestSubmit().then(res => { + setIsSubmitting(false); if (res && res.payload && res.payload.data && !res.error) { history.push(`/feedback/submit/confirmation/?request=${requestId}`); } else { @@ -225,7 +229,7 @@ const FeedbackSubmitForm = ({ - - - Date: Wed, 6 Nov 2024 14:31:41 -0600 Subject: [PATCH 20/23] Added a switch to filter inactive teams (default). --- .../components/team-results/TeamResults.jsx | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/web-ui/src/components/team-results/TeamResults.jsx b/web-ui/src/components/team-results/TeamResults.jsx index 427ac9969..bf6df701e 100644 --- a/web-ui/src/components/team-results/TeamResults.jsx +++ b/web-ui/src/components/team-results/TeamResults.jsx @@ -8,7 +8,7 @@ import { } from '../../context/selectors'; import TeamsActions from './TeamsActions'; import PropTypes from 'prop-types'; -import { TextField } from '@mui/material'; +import { FormControlLabel, Switch, TextField } from '@mui/material'; import './TeamResults.css'; import SkeletonLoader from '../skeleton_loader/SkeletonLoader'; import { useQueryParameters } from '../../helpers/query-parameters'; @@ -40,22 +40,11 @@ const TeamResults = () => { const { state } = useContext(AppContext); const loading = selectTeamsLoading(state); const [addingTeam, setAddingTeam] = useState(false); + const [activeTeams, setActiveTeams] = useState(true); const [searchText, setSearchText] = useState(''); const [selectedTeamId, setSelectedTeamId] = useState(''); const teams = selectNormalizedTeams(state, searchText); - const teamCards = teams.map((team, index) => { - return ( - - ); - }); - useQueryParameters([ { name: 'addNew', @@ -89,16 +78,41 @@ const TeamResults = () => { setSearchText(e.target.value); }} /> - +
+ + { + const { checked } = event.target; + setActiveTeams(checked); + }} + /> + } + label="Active Teams Only" + /> +
{loading ? Array.from({ length: 20 }).map((_, index) => ( )) - : teams?.length && !loading - ? teamCards - : null} + : teams.filter((team) => !activeTeams || + (activeTeams && team.active)) + .map((team, index) => { + return ( + + ); + }) + }
); From a76cffe5adc6b12a8602ec2f8c363fd000fb9d42 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 6 Nov 2024 14:32:18 -0600 Subject: [PATCH 21/23] Use a check box to mark guilds active/inactive to be consistent with the existing team edit functionality. --- .../guild-results/EditGuildModal.jsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/web-ui/src/components/guild-results/EditGuildModal.jsx b/web-ui/src/components/guild-results/EditGuildModal.jsx index 9e8ecebec..79dfeef90 100644 --- a/web-ui/src/components/guild-results/EditGuildModal.jsx +++ b/web-ui/src/components/guild-results/EditGuildModal.jsx @@ -11,7 +11,8 @@ import { FormControlLabel, Modal, Switch, - TextField + TextField, + Checkbox, } from '@mui/material'; import Autocomplete from '@mui/material/Autocomplete'; import './EditGuildModal.css'; @@ -166,18 +167,18 @@ const EditGuildModal = ({ guild = {}, open, onSave, onClose, headerText }) => { value={editedGuild.name ? editedGuild.name : ''} onChange={e => setGuild({ ...editedGuild, name: e.target.value })} /> - {guild.id && { - const { checked } = event.target; - setGuild({ ...editedGuild, active: checked }); - }} - /> - } + {guild.id && (<>} + variant="outlined" + className="halfWidth" + checked={editedGuild.active} + onChange={event => { + const { checked } = event.target; + setGuild({ ...editedGuild, active: checked }); + }} + /> Active + )}
Date: Wed, 6 Nov 2024 14:39:42 -0600 Subject: [PATCH 22/23] For some reason, using just editedGuild.active in the checked prop for Checkbox did not work. --- web-ui/src/components/guild-results/EditGuildModal.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web-ui/src/components/guild-results/EditGuildModal.jsx b/web-ui/src/components/guild-results/EditGuildModal.jsx index 79dfeef90..953c5ccfb 100644 --- a/web-ui/src/components/guild-results/EditGuildModal.jsx +++ b/web-ui/src/components/guild-results/EditGuildModal.jsx @@ -167,12 +167,13 @@ const EditGuildModal = ({ guild = {}, open, onSave, onClose, headerText }) => { value={editedGuild.name ? editedGuild.name : ''} onChange={e => setGuild({ ...editedGuild, name: e.target.value })} /> - {guild.id && (<> + { const { checked } = event.target; setGuild({ ...editedGuild, active: checked }); From fe5ba4915dd7ca0897d29c093c1a719f7d3f2127 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 6 Nov 2024 14:45:14 -0600 Subject: [PATCH 23/23] Updated to reflect active teams switch. --- .../__snapshots__/TeamResults.test.jsx.snap | 142 +++++++++++++----- 1 file changed, 106 insertions(+), 36 deletions(-) diff --git a/web-ui/src/components/team-results/__snapshots__/TeamResults.test.jsx.snap b/web-ui/src/components/team-results/__snapshots__/TeamResults.test.jsx.snap index dc34d6284..85eb1e6ed 100644 --- a/web-ui/src/components/team-results/__snapshots__/TeamResults.test.jsx.snap +++ b/web-ui/src/components/team-results/__snapshots__/TeamResults.test.jsx.snap @@ -47,31 +47,66 @@ exports[`renders correctly 1`] = `
- +
+
- +
+