From 1d2022829aacd683f36d8065cf136cc701e360b4 Mon Sep 17 00:00:00 2001 From: Will Thelen Date: Tue, 8 Oct 2024 11:07:04 -0500 Subject: [PATCH 01/10] Added Feedback Request 'Deny' option --- .../ReceivedRequestCard.jsx | 47 ++-- .../ViewFeedbackResponses.jsx | 56 ++++- .../FeedbackResponseCard.jsx | 57 ++--- web-ui/src/pages/ReceivedRequestsPage.jsx | 232 ++++++------------ 4 files changed, 167 insertions(+), 225 deletions(-) diff --git a/web-ui/src/components/received_request_card/ReceivedRequestCard.jsx b/web-ui/src/components/received_request_card/ReceivedRequestCard.jsx index 279029b8c..a5eae870d 100644 --- a/web-ui/src/components/received_request_card/ReceivedRequestCard.jsx +++ b/web-ui/src/components/received_request_card/ReceivedRequestCard.jsx @@ -7,7 +7,7 @@ import PropTypes from 'prop-types'; import { selectProfile } from '../../context/selectors'; import DateFnsUtils from '@date-io/date-fns'; import { AppContext } from '../../context/AppContext'; -import { Edit as EditIcon } from '@mui/icons-material'; +import { Edit as EditIcon, Close as CloseIcon } from '@mui/icons-material'; import './ReceivedRequestCard.css'; const dateFns = new DateFnsUtils(); @@ -21,12 +21,16 @@ const classes = { }; const propTypes = { - request: PropTypes.object.isRequired + request: PropTypes.object.isRequired, + handleDenyClick: PropTypes.func.isRequired // handle deny function as a prop }; -const ReceivedRequestCard = ({ request }) => { - let { submitDate, dueDate, sendDate } = request; +const ReceivedRequestCard = ({ request, handleDenyClick }) => { + console.log("Rendering ReceivedRequestCard for request:", request.id); + const { state } = useContext(AppContext); + + let { submitDate, dueDate, sendDate } = request; const requestCreator = selectProfile(state, request?.creatorId); const requestee = selectProfile(state, request?.requesteeId); submitDate = submitDate @@ -67,10 +71,7 @@ const ReceivedRequestCard = ({ request }) => { }; return ( -
+
{ src={getAvatarURL(requestCreator?.workEmail)} />
- - {requestCreator?.name} - + {requestCreator?.name} {requestCreator?.title} @@ -119,22 +118,30 @@ const ReceivedRequestCard = ({ request }) => { !request.submitDate && request.id && request.status !== 'canceled' ? ( - - - - + <> + + + + + + + + + { + console.log("X icon clicked for request ID:", request.id); + handleDenyClick(request.id); // Call handleDenyClick with request ID + }}> + - + ) : null}
); }; + ReceivedRequestCard.propTypes = propTypes; -export default ReceivedRequestCard; +export default ReceivedRequestCard; \ No newline at end of file diff --git a/web-ui/src/components/view_feedback_responses/ViewFeedbackResponses.jsx b/web-ui/src/components/view_feedback_responses/ViewFeedbackResponses.jsx index e816173b5..bbfbe1ce1 100644 --- a/web-ui/src/components/view_feedback_responses/ViewFeedbackResponses.jsx +++ b/web-ui/src/components/view_feedback_responses/ViewFeedbackResponses.jsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useState } from 'react'; import { styled } from '@mui/material/styles'; -import { Autocomplete, Avatar, Button, Checkbox, Chip, TextField, Typography } from '@mui/material'; +import { Autocomplete, Avatar, Button, Checkbox, Chip, TextField, Typography, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; import FeedbackResponseCard from './feedback_response_card/FeedbackResponseCard'; import { getQuestionsAndAnswers } from '../../api/feedbackanswer'; import { getFeedbackRequestById } from '../../api/feedback'; @@ -50,7 +50,6 @@ const Root = styled('div')({ marginRight: '3em', width: '350px', ['@media (max-width: 800px)']: { - // eslint-disable-line no-useless-computed-key marginRight: 0, width: '100%' } @@ -58,7 +57,6 @@ const Root = styled('div')({ [`& .${classes.responderField}`]: { minWidth: '500px', ['@media (max-width: 800px)']: { - // eslint-disable-line no-useless-computed-key minWidth: 0, width: '100%' } @@ -79,6 +77,28 @@ const ViewFeedbackResponses = () => { useState([]); const [isLoading, setIsLoading] = useState(true); + // Dialog state for denying feedback + const [denialPopupOpen, setDenialPopupOpen] = useState(false); + const [denialReason, setDenialReason] = useState(''); + const [currentResponder, setCurrentResponder] = useState(null); // Track the responder being denied + + const handleDenyClick = (responderId) => { + console.log(`Denial process initiated for responder: ${responderId}`); + setCurrentResponder(responderId); // Track the current responder + setDenialPopupOpen(true); + }; + + const handleDenialClose = () => { + setDenialPopupOpen(false); + console.log("Denial popup closed"); + }; + + const handleDenialSubmit = () => { + console.log(`Denial reason for responder ${currentResponder}:`, denialReason); + setDenialPopupOpen(false); + // Here, you'd normally send the reason to the backend or handle it appropriately + }; + useEffect(() => { setQuery(queryString.parse(location?.search)); }, [location.search]); @@ -136,7 +156,6 @@ const ViewFeedbackResponses = () => { }); }, [csrf, query.request]); - // Sets the options for filtering by responders useEffect(() => { let allResponders = []; questionsAndAnswers.forEach(({ answers }) => { @@ -147,7 +166,6 @@ const ViewFeedbackResponses = () => { setResponderOptions(allResponders); }, [state, questionsAndAnswers]); - // Populate all responders as selected by default useEffect(() => { setSelectedResponders(responderOptions); }, [responderOptions]); @@ -156,12 +174,10 @@ const ViewFeedbackResponses = () => { let responsesToDisplay = [...questionsAndAnswers]; responsesToDisplay = responsesToDisplay.map(response => { - // Filter based on selected responders let filteredAnswers = response.answers.filter(answer => selectedResponders.includes(answer.responder) ); if (searchText.trim()) { - // Filter based on search text filteredAnswers = filteredAnswers.filter( ({ answer }) => answer && @@ -172,7 +188,7 @@ const ViewFeedbackResponses = () => { }); setFilteredQuestionsAndAnswers(responsesToDisplay); - }, [searchText, selectedResponders]); // eslint-disable-line react-hooks/exhaustive-deps + }, [searchText, selectedResponders]); useEffect(() => { if (isLoading && filteredQuestionsAndAnswers.length > 0) { @@ -310,15 +326,37 @@ const ViewFeedbackResponses = () => { answer={answer.answer || ''} inputType={question.inputType} sentiment={answer.sentiment} + handleDenyClick={() => handleDenyClick(answer.responder)} // Pass responderId /> ))}
); })} + + {/* Dialog for denial reason */} + + Feedback Request Denial Explanation + + setDenialReason(e.target.value)} + /> + + + + + + ) : (

{noPermission}

); }; -export default ViewFeedbackResponses; +export default ViewFeedbackResponses; \ No newline at end of file diff --git a/web-ui/src/components/view_feedback_responses/feedback_response_card/FeedbackResponseCard.jsx b/web-ui/src/components/view_feedback_responses/feedback_response_card/FeedbackResponseCard.jsx index 42a14d3c3..35908712b 100644 --- a/web-ui/src/components/view_feedback_responses/feedback_response_card/FeedbackResponseCard.jsx +++ b/web-ui/src/components/view_feedback_responses/feedback_response_card/FeedbackResponseCard.jsx @@ -1,46 +1,35 @@ -import React, { useContext } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; -import { Typography } from '@mui/material'; +import { Typography, IconButton } from '@mui/material'; +import { Close as CloseIcon } from '@mui/icons-material'; import './FeedbackResponseCard.css'; -import { AppContext } from '../../../context/AppContext'; -import { selectProfile } from '../../../context/selectors'; -import Avatar from '@mui/material/Avatar'; -import { getAvatarURL } from '../../../api/api.js'; -import FeedbackAnswerInput from '../../feedback_answer_input/FeedbackAnswerInput'; - -const propTypes = { - responderId: PropTypes.string.isRequired, - answer: PropTypes.string.isRequired, - inputType: PropTypes.string.isRequired, - sentiment: PropTypes.number -}; - -const FeedbackResponseCard = props => { - const { state } = useContext(AppContext); - const userInfo = selectProfile(state, props.responderId); +const FeedbackResponseCard = ({ responderId, answer, inputType, sentiment, handleDenyClick }) => { + console.log("Rendering FeedbackResponseCard"); return ( - - -
- - {userInfo?.name} -
- + + + Responder: {responderId} + Answer: {answer} + { + console.log(`Deny click for responder ID: ${responderId}`); + handleDenyClick(); + }}> + + ); }; -FeedbackResponseCard.propTypes = propTypes; +FeedbackResponseCard.propTypes = { + responderId: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + inputType: PropTypes.string.isRequired, + sentiment: PropTypes.number, + handleDenyClick: PropTypes.func.isRequired +}; -export default FeedbackResponseCard; +export default FeedbackResponseCard; \ No newline at end of file diff --git a/web-ui/src/pages/ReceivedRequestsPage.jsx b/web-ui/src/pages/ReceivedRequestsPage.jsx index c7a97e97e..5828c5673 100644 --- a/web-ui/src/pages/ReceivedRequestsPage.jsx +++ b/web-ui/src/pages/ReceivedRequestsPage.jsx @@ -11,7 +11,7 @@ import TextField from '@mui/material/TextField'; import MenuItem from '@mui/material/MenuItem'; import Typography from '@mui/material/Typography'; import { Search as SearchIcon } from '@mui/icons-material'; -import { Collapse, InputAdornment } from '@mui/material'; +import { Collapse, InputAdornment, Dialog, DialogActions, DialogContent, DialogTitle, Button } from '@mui/material'; import ReceivedRequestCard from '../components/received_request_card/ReceivedRequestCard'; import { getFeedbackRequestsByRecipient } from '../api/feedback'; import './ReceivedRequestsPage.css'; @@ -85,16 +85,37 @@ const ReceivedRequestsPage = () => { const [filteredCanceledRequests, setFilteredCanceledRequests] = useState([]); const [filteredReceivedRequests, setFilteredReceivedRequests] = useState([]); - const [filteredSubmittedRequests, setFilteredSubmittedRequests] = useState( - [] - ); + const [filteredSubmittedRequests, setFilteredSubmittedRequests] = useState([]); + + const [canceledRequestsExpanded, setCanceledRequestsExpanded] = useState(false); + const [receivedRequestsExpanded, setReceivedRequestsExpanded] = useState(true); + const [submittedRequestsExpanded, setSubmittedRequestsExpanded] = useState(false); + + // State for denial dialog + const [denialPopupOpen, setDenialPopupOpen] = useState(false); + const [denialReason, setDenialReason] = useState(''); + const [currentRequestId, setCurrentRequestId] = useState(null); + + const handleDenyClick = (requestId) => { + console.log("Denial process initiated for request ID:", requestId); + setCurrentRequestId(requestId); + setDenialPopupOpen(true); + }; - const [canceledRequestsExpanded, setCanceledRequestsExpanded] = - useState(false); - const [receivedRequestsExpanded, setReceivedRequestsExpanded] = - useState(true); - const [submittedRequestsExpanded, setSubmittedRequestsExpanded] = - useState(false); + const handleDenialClose = () => { + setDenialPopupOpen(false); + setDenialReason(''); + setCurrentRequestId(null); + console.log("Denial popup closed"); + }; + + const handleDenialSubmit = () => { + console.log("Denial reason for request ID:", currentRequestId, denialReason); + // Perform any necessary action for submitting the denial reason here + setDenialPopupOpen(false); + setDenialReason(''); + setCurrentRequestId(null); + }; useEffect(() => { const getAllFeedbackRequests = async () => { @@ -116,44 +137,27 @@ const ReceivedRequestsPage = () => { getAllFeedbackRequests().then(data => { if (data) { setCanceledRequests(data.filter(req => req.status === 'canceled')); - setReceivedRequests( - data.filter(req => !req.submitDate && req.status !== 'canceled') - ); - setSubmittedRequests( - data.filter( - req => - req.submitDate && - req.submitDate.length === 3 && - req.status !== 'canceled' - ) - ); + setReceivedRequests(data.filter(req => !req.submitDate && req.status !== 'canceled')); + setSubmittedRequests(data.filter(req => req.submitDate && req.submitDate.length === 3 && req.status !== 'canceled')); setIsLoading(false); } }); } }, [csrf, currentUserId]); + useEffect(() => { let filteredCanceled = [...canceledRequests]; let filteredReceived = [...receivedRequests]; let filteredSubmitted = [...submittedRequests]; - // Search for intersection of multiple queries separated by commas - const queries = searchText - .split(',') - .map(search => search.trim().toLowerCase()); + const queries = searchText.split(',').map(search => search.trim().toLowerCase()); if (searchText.trim()) { for (let query of queries) { const setFiltered = filteredOption => { return filteredOption.filter(request => { - const creatorName = selectProfile( - state, - request.creatorId - ).name.toLowerCase(); - const requesteeName = selectProfile( - state, - request.requesteeId - ).name.toLowerCase(); + const creatorName = selectProfile(state, request.creatorId).name.toLowerCase(); + const requesteeName = selectProfile(state, request.requesteeId).name.toLowerCase(); return creatorName.includes(query) || requesteeName.includes(query); }); }; @@ -163,16 +167,13 @@ const ReceivedRequestsPage = () => { } } - // Sort according to selected sort option let sortMethod; switch (sortValue) { case SortOption.SEND_DATE_ASCENDING: - sortMethod = (a, b) => - new Date(a.sendDate) > new Date(b.sendDate) ? 1 : -1; + sortMethod = (a, b) => new Date(a.sendDate) > new Date(b.sendDate) ? 1 : -1; break; case SortOption.SEND_DATE_DESCENDING: - sortMethod = (a, b) => - new Date(a.sendDate) > new Date(b.sendDate) ? -1 : 1; + sortMethod = (a, b) => new Date(a.sendDate) > new Date(b.sendDate) ? -1 : 1; break; case SortOption.DUE_DATE: sortMethod = (a, b) => { @@ -183,9 +184,7 @@ const ReceivedRequestsPage = () => { }; break; default: - console.warn( - `Invalid sort option ${sortValue} provided for received requests` - ); + console.warn(`Invalid sort option ${sortValue} provided for received requests`); } filteredCanceled.sort(sortMethod); @@ -195,14 +194,7 @@ const ReceivedRequestsPage = () => { setFilteredCanceledRequests(filteredCanceled); setFilteredReceivedRequests(filteredReceived); setFilteredSubmittedRequests(filteredSubmitted); - }, [ - state, - canceledRequests, - receivedRequests, - submittedRequests, - searchText, - sortValue - ]); + }, [state, canceledRequests, receivedRequests, submittedRequests, searchText, sortValue]); return ( @@ -252,6 +244,8 @@ const ReceivedRequestsPage = () => {
+ + {/* Received Requests */}
Received Requests {
- {receivedRequests.length} received request - {receivedRequests.length === 1 ? '' : 's'} currently hidden + {receivedRequests.length} received request{receivedRequests.length === 1 ? '' : 's'} currently hidden
@@ -283,125 +276,40 @@ const ReceivedRequestsPage = () => {
{filteredReceivedRequests.length === 0 && (
- - No received feedback requests - + No received feedback requests
)} {filteredReceivedRequests.map(request => ( - - ))} -
- )} - -
- Submitted Requests - - setSubmittedRequestsExpanded(!submittedRequestsExpanded) - } - aria-expanded={submittedRequestsExpanded} - aria-label={submittedRequestsExpanded ? 'show less' : 'show more'} - size="large" - /> -
- - - {isLoading && ( -
- {Array.from({ length: 1 }).map((_, index) => ( - - ))} -
- )} - {!isLoading && ( -
- - {submittedRequests.length} submitted request - {submittedRequests.length === 1 ? '' : 's'} currently hidden - -
- )} -
- - {!isLoading && ( -
- {submittedRequests.length === 0 && ( -
- - No submitted feedback requests - -
- )} - {submittedRequests.length > 0 && - filteredSubmittedRequests.length === 0 && ( -
- - No submitted feedback requests - -
- )} - {filteredSubmittedRequests.map(request => ( - - ))} -
- )} -
-
- Canceled Requests - setCanceledRequestsExpanded(!canceledRequestsExpanded)} - aria-expanded={canceledRequestsExpanded} - aria-label={canceledRequestsExpanded ? 'show less' : 'show more'} - size="large" - /> -
- - - {isLoading && ( -
- {Array.from({ length: 1 }).map((_, index) => ( - - ))} -
- )} - {!isLoading && ( -
- - {canceledRequests.length} canceled request - {canceledRequests.length === 1 ? '' : 's'} currently hidden - -
- )} -
- - {!isLoading && ( -
- {canceledRequests.length === 0 && ( -
- - No canceled feedback requests - -
- )} - {canceledRequests.length > 0 && - filteredCanceledRequests.length === 0 && ( -
- - No canceled feedback requests - -
- )} - {filteredCanceledRequests.map(request => ( - + ))}
)}
+ + {/* Dialog for denial reason */} + + Feedback Request Denial Explanation + + setDenialReason(e.target.value)} + /> + + + + + + + + {/* Additional sections for Submitted and Canceled requests here... */} ); }; -export default ReceivedRequestsPage; +export default ReceivedRequestsPage; \ No newline at end of file From 7427a4614feba806158f331f3b14b0eb8780ae50 Mon Sep 17 00:00:00 2001 From: Will Thelen Date: Tue, 8 Oct 2024 20:06:32 -0500 Subject: [PATCH 02/10] Fixed snapshots --- .../FeedbackResponseCard.test.tsx.snap | 240 +++++++----------- 1 file changed, 93 insertions(+), 147 deletions(-) diff --git a/web-ui/src/components/view_feedback_responses/feedback_response_card/__snapshots__/FeedbackResponseCard.test.tsx.snap b/web-ui/src/components/view_feedback_responses/feedback_response_card/__snapshots__/FeedbackResponseCard.test.tsx.snap index c334a0f5c..540b6aff0 100644 --- a/web-ui/src/components/view_feedback_responses/feedback_response_card/__snapshots__/FeedbackResponseCard.test.tsx.snap +++ b/web-ui/src/components/view_feedback_responses/feedback_response_card/__snapshots__/FeedbackResponseCard.test.tsx.snap @@ -3,128 +3,44 @@ exports[`FeedbackResponseCard > renders correctly for radio button responses 1`] = `
-
-
+

+ Answer: + Yes +

+
-

+ -

- +
@@ -133,26 +49,44 @@ exports[`FeedbackResponseCard > renders correctly for radio button responses 1`] exports[`FeedbackResponseCard > renders correctly for slider responses 1`] = `
-
+ Responder: + 01b7d769-9fa2-43ff-95c7-f3b950a27bf9 + +

-

+
-

+ -

+
@@ -161,32 +95,44 @@ exports[`FeedbackResponseCard > renders correctly for slider responses 1`] = ` exports[`FeedbackResponseCard > renders correctly for text responses 1`] = `
-
+ Responder: + 01b7d769-9fa2-43ff-95c7-f3b950a27bf9 + +

+ Answer: + I love opossums. I have rehabilitated baby opossums for 25 years, and I intend to do so until my last day! +

+
-
-        I love opossums. I have rehabilitated baby opossums for 25 years, and I intend to do so until my last day!
-      
+
From 47ec47c7bc2c998ac82cb58e37a260ffa7ddfeaa Mon Sep 17 00:00:00 2001 From: thelenw Date: Mon, 11 Nov 2024 13:13:18 -0600 Subject: [PATCH 03/10] Pushing broken state to request help identifying ambiguous 400 error from feedback_request backend service. --- .../feedback_request/DTO/CreatorDTO.java | 27 ++++++ .../feedback_request/DTO/DenierDTO.java | 40 ++++++++ .../DTO/DenyFeedbackRequestDTO.java | 54 +++++++++++ .../feedback_request/FeedbackRequest.java | 27 +++++- .../FeedbackRequestController.java | 77 ++++++++++++++- .../FeedbackRequestCreateDTO.java | 8 ++ .../FeedbackRequestResponseDTO.java | 11 +++ .../FeedbackRequestServices.java | 3 + .../FeedbackRequestServicesImpl.java | 3 +- .../FeedbackRequestUpdateDTO.java | 7 ++ .../notification/NotificationController.java | 45 +++++++++ .../notification/NotificationDTO.java | 33 +++++++ .../notification/NotificationService.java | 8 ++ .../notification/NotificationServiceImpl.java | 65 +++++++++++++ .../services/permissions/Permission.java | 2 + .../reviews/ReviewPeriodServicesImpl.java | 3 +- .../V116__alter_feedback_requests_table.sql | 10 ++ .../V117__alter_feedback_requests_table.sql | 4 + .../resources/db/dev/R__Load_testing_data.sql | 10 ++ web-ui/src/api/feedback.js | 41 ++++++++ .../FeedbackRequestCard.css | 28 ++++++ .../FeedbackRequestCard.jsx | 6 +- .../ReceivedRequestCard.css | 5 + .../ReceivedRequestCard.jsx | 17 +++- .../ViewFeedbackResponses.jsx | 51 +++++++++- web-ui/src/pages/ReceivedRequestsPage.jsx | 78 +++++++++++++-- web-ui/src/pages/ViewFeedbackPage.jsx | 96 +++++++++++++------ 27 files changed, 698 insertions(+), 61 deletions(-) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/CreatorDTO.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenierDTO.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenyFeedbackRequestDTO.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationController.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationDTO.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationService.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationServiceImpl.java create mode 100644 server/src/main/resources/db/common/V116__alter_feedback_requests_table.sql create mode 100644 server/src/main/resources/db/common/V117__alter_feedback_requests_table.sql diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/CreatorDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/CreatorDTO.java new file mode 100644 index 000000000..cd222a93e --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/CreatorDTO.java @@ -0,0 +1,27 @@ +package com.objectcomputing.checkins.services.feedback_request.DTO; + +import jakarta.validation.constraints.NotNull; +import java.util.UUID; + +public class CreatorDTO { + + @NotNull(message = "Creator ID cannot be null") + private UUID id; + + // Constructors + public CreatorDTO() {} + + public CreatorDTO(UUID id) { + this.id = id; + } + + // Getters + public UUID getId() { + return id; + } + + // Setters + public void setId(UUID id) { + this.id = id; + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenierDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenierDTO.java new file mode 100644 index 000000000..46fb707a0 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenierDTO.java @@ -0,0 +1,40 @@ +package com.objectcomputing.checkins.services.feedback_request.DTO; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.UUID; + +public class DenierDTO { + + @NotNull(message = "Denier ID cannot be null") + private UUID id; + + @NotBlank(message = "Denier name cannot be blank") + private String name; + + // Constructors + public DenierDTO() {} + + public DenierDTO(UUID id, String name) { + this.id = id; + this.name = name; + } + + // Getters + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + // Setters + public void setId(UUID id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenyFeedbackRequestDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenyFeedbackRequestDTO.java new file mode 100644 index 000000000..5ea1a256a --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenyFeedbackRequestDTO.java @@ -0,0 +1,54 @@ +package com.objectcomputing.checkins.services.feedback_request.DTO; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class DenyFeedbackRequestDTO { + + @NotBlank(message = "Reason cannot be blank") + private String reason; + + @NotNull(message = "Denier cannot be null") + @Valid + private DenierDTO denier; + + @NotNull(message = "Creator cannot be null") + @Valid + private CreatorDTO creator; + + // Constructors + public DenyFeedbackRequestDTO() {} + + public DenyFeedbackRequestDTO(String reason, DenierDTO denier, CreatorDTO creator) { + this.reason = reason; + this.denier = denier; + this.creator = creator; + } + + // Getters + public String getReason() { + return reason; + } + + public DenierDTO getDenier() { + return denier; + } + + public CreatorDTO getCreator() { + return creator; + } + + // Setters + public void setReason(String reason) { + this.reason = reason; + } + + public void setDenier(DenierDTO denier) { + this.denier = denier; + } + + public void setCreator(CreatorDTO creator) { + this.creator = creator; + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequest.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequest.java index 5753f7b98..2d83aed71 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequest.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequest.java @@ -12,6 +12,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Null; import lombok.Getter; import lombok.Setter; @@ -86,6 +87,18 @@ public class FeedbackRequest { @Schema(description = "the id of the review period in that this request was created for") private UUID reviewPeriodId; + @Column(name = "denied") + @Nullable + @Schema(description = "Whether the feedback request has been denied") + private boolean denied = false; + + @Column(name = "reason") + @Nullable + @Schema(description = "Denial reason") + private String reason; + + + public FeedbackRequest(UUID creatorId, UUID requesteeId, UUID recipientId, @@ -94,7 +107,9 @@ public FeedbackRequest(UUID creatorId, @Nullable LocalDate dueDate, String status, @Nullable LocalDate submitDate, - @Nullable UUID reviewPeriodId) { + @Nullable UUID reviewPeriodId, + boolean denied, + @Nullable String reason) { this.id = null; this.creatorId = creatorId; this.requesteeId = requesteeId; @@ -105,6 +120,8 @@ public FeedbackRequest(UUID creatorId, this.status = status; this.submitDate = submitDate; this.reviewPeriodId = reviewPeriodId; + this.denied = denied; + this.reason = reason; } public FeedbackRequest() {} @@ -123,12 +140,14 @@ public boolean equals(Object o) { && Objects.equals(dueDate, that.dueDate) && Objects.equals(status, that.status) && Objects.equals(submitDate, that.submitDate) - && Objects.equals(reviewPeriodId, that.reviewPeriodId); + && Objects.equals(reviewPeriodId, that.reviewPeriodId) + && Objects.equals(denied, that.denied) + && Objects.equals(reason, that.reason); } @Override public int hashCode() { - return Objects.hash(id, creatorId, recipientId, requesteeId, sendDate, templateId, dueDate, status, submitDate, reviewPeriodId); + return Objects.hash(id, creatorId, recipientId, requesteeId, sendDate, templateId, dueDate, status, submitDate, reviewPeriodId, denied, reason); } @Override @@ -144,6 +163,8 @@ public String toString() { ", status='" + status + ", submitDate='" + submitDate + ", reviewPeriodId='" + reviewPeriodId + + ", denied='" + denied + + ", reason='" + reason + '}'; } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java index 1e2fa3a27..409dacef2 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java @@ -2,6 +2,10 @@ import com.objectcomputing.checkins.services.permissions.Permission; import com.objectcomputing.checkins.services.permissions.RequiredPermission; +import com.objectcomputing.checkins.services.feedback_request.DTO.DenyFeedbackRequestDTO; +import com.objectcomputing.checkins.services.feedback_request.DTO.DenierDTO; +import com.objectcomputing.checkins.services.feedback_request.DTO.CreatorDTO; +import com.objectcomputing.checkins.services.notification.NotificationService; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.format.Format; import io.micronaut.http.HttpResponse; @@ -10,6 +14,7 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Delete; import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.Put; import io.micronaut.http.annotation.Status; @@ -19,6 +24,7 @@ import io.micronaut.security.rules.SecurityRule; import io.micronaut.validation.Validated; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; @@ -35,9 +41,12 @@ public class FeedbackRequestController { private final FeedbackRequestServices feedbackReqServices; + private final NotificationService notificationService; - public FeedbackRequestController(FeedbackRequestServices feedbackReqServices) { + @Inject + public FeedbackRequestController(FeedbackRequestServices feedbackReqServices, NotificationService notificationService) { this.feedbackReqServices = feedbackReqServices; + this.notificationService = notificationService; } /** @@ -91,7 +100,7 @@ public void delete(@NotNull UUID id) { public HttpResponse getById(UUID id) { FeedbackRequest savedFeedbackRequest = feedbackReqServices.getById(id); return savedFeedbackRequest == null ? HttpResponse.notFound() : HttpResponse.ok(fromEntity(savedFeedbackRequest)) - .headers(headers -> headers.location(URI.create("/feedback_request" + savedFeedbackRequest.getId()))); + .headers(headers -> headers.location(URI.create("/feedback_request/" + savedFeedbackRequest.getId()))); } /** @@ -106,13 +115,67 @@ public HttpResponse getById(UUID id) { */ @RequiredPermission(Permission.CAN_VIEW_FEEDBACK_REQUEST) @Get("/{?creatorId,requesteeId,recipientId,oldestDate,reviewPeriodId,templateId,requesteeIds}") - public List findByValues(@Nullable UUID creatorId, @Nullable UUID requesteeId, @Nullable UUID recipientId, @Nullable @Format("yyyy-MM-dd") LocalDate oldestDate, @Nullable UUID reviewPeriodId, @Nullable UUID templateId, @Nullable List requesteeIds) { + public List findByValues( + @Nullable UUID creatorId, + @Nullable UUID requesteeId, + @Nullable UUID recipientId, + @Nullable @Format("yyyy-MM-dd") LocalDate oldestDate, + @Nullable UUID reviewPeriodId, + @Nullable UUID templateId, + @Nullable List requesteeIds) { return feedbackReqServices.findByValues(creatorId, requesteeId, recipientId, oldestDate, reviewPeriodId, templateId, requesteeIds) .stream() .map(this::fromEntity) .toList(); } + /** + * Deny a feedback request + * + * @param id {@link UUID} ID of the feedback request to deny + * @param body Request body containing reason, denier, and creator information + * @return {@link FeedbackRequestResponseDTO} with updated denial status + */ + @Post("/{id}/deny") +@RequiredPermission(Permission.CAN_DENY_FEEDBACK_REQUEST) +public HttpResponse denyFeedbackRequest( + @PathVariable("id") @NotNull UUID id, + @Body @Valid DenyFeedbackRequestDTO body +) { + FeedbackRequest feedbackRequest = feedbackReqServices.getById(id); + if (feedbackRequest == null) { + return HttpResponse.notFound(); + } + + String reason = body.getReason(); + DenierDTO denier = body.getDenier(); + CreatorDTO creator = body.getCreator(); + + if (!feedbackRequest.isDenied() && reason != null && !reason.trim().isEmpty()) { + FeedbackRequestUpdateDTO dto = new FeedbackRequestUpdateDTO(); + dto.setId(feedbackRequest.getId()); + dto.setDueDate(feedbackRequest.getDueDate()); + dto.setStatus(feedbackRequest.getStatus()); + dto.setSubmitDate(feedbackRequest.getSubmitDate()); + dto.setRecipientId(feedbackRequest.getRecipientId()); + dto.setDenied(true); + dto.setReason(reason); + + FeedbackRequest updatedFeedbackRequest = feedbackReqServices.update(dto); + + UUID creatorId = creator.getId(); + String denierName = denier.getName(); + notificationService.sendNotification( + creatorId, + String.format("Your feedback request was denied by %s. Reason: %s", denierName, reason) + ); + + return HttpResponse.ok(fromEntity(updatedFeedbackRequest)); + } + + return HttpResponse.ok(fromEntity(feedbackRequest)); +} + private FeedbackRequestResponseDTO fromEntity(FeedbackRequest feedbackRequest) { FeedbackRequestResponseDTO dto = new FeedbackRequestResponseDTO(); dto.setId(feedbackRequest.getId()); @@ -125,7 +188,8 @@ private FeedbackRequestResponseDTO fromEntity(FeedbackRequest feedbackRequest) { dto.setStatus(feedbackRequest.getStatus()); dto.setSubmitDate(feedbackRequest.getSubmitDate()); dto.setReviewPeriodId(feedbackRequest.getReviewPeriodId()); - + dto.setDenied(feedbackRequest.isDenied()); + dto.setReason(feedbackRequest.getReason()); return dto; } @@ -139,6 +203,9 @@ private FeedbackRequest fromDTO(FeedbackRequestCreateDTO dto) { dto.getDueDate(), dto.getStatus(), dto.getSubmitDate(), - dto.getReviewPeriodId()); + dto.getReviewPeriodId(), + dto.isDenied(), + dto.getReason() + ); } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestCreateDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestCreateDTO.java index 91674b498..3ecda5b80 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestCreateDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestCreateDTO.java @@ -4,6 +4,7 @@ import io.micronaut.core.annotation.Nullable; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Null; import lombok.Getter; import lombok.Setter; @@ -51,5 +52,12 @@ public class FeedbackRequestCreateDTO { @Schema(description = "the id of the review period in that this request was created for") private UUID reviewPeriodId; + @Schema(description = "Whether the feedback request has been denied") + private boolean denied = false; + + @Nullable + @Schema(description = "Reason for the request being denied") + private String reason; + } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestResponseDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestResponseDTO.java index 2b3f0a145..584b54d13 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestResponseDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestResponseDTO.java @@ -55,4 +55,15 @@ public class FeedbackRequestResponseDTO { @Schema(description = "the id of the review period in that this request was created for") private UUID reviewPeriodId; + @Schema(description = "Whether the feedback request has been denied") + private boolean denied = false; + + @Nullable + @Schema(description = "Reason for the request being denied") + private String reason; + + public FeedbackRequestResponseDTO() { + this.denied = false; + } + } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServices.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServices.java index 90cfd09ce..f5c0a844f 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServices.java @@ -4,6 +4,8 @@ import java.util.List; import java.util.UUID; +import io.micronaut.http.HttpResponse; + public interface FeedbackRequestServices { FeedbackRequest save(FeedbackRequest feedbackRequest); @@ -14,4 +16,5 @@ public interface FeedbackRequestServices { FeedbackRequest getById(UUID id); List findByValues(UUID creatorId, UUID requesteeId, UUID recipientId, LocalDate oldestDate, UUID reviewPeriodId, UUID templateId, List requesteeIds); + } \ No newline at end of file diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java index 180d8fef3..0fa68e327 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java @@ -371,7 +371,8 @@ private FeedbackRequest getFromDTO(FeedbackRequestUpdateDTO dto) { feedbackRequest.setStatus(dto.getStatus()); feedbackRequest.setSubmitDate(dto.getSubmitDate()); feedbackRequest.setRecipientId(dto.getRecipientId()); - + feedbackRequest.setDenied(dto.isDenied()); + feedbackRequest.setReason(dto.getReason()); return feedbackRequest; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestUpdateDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestUpdateDTO.java index 6c25849ec..e1c0637ad 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestUpdateDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestUpdateDTO.java @@ -35,4 +35,11 @@ public class FeedbackRequestUpdateDTO { @Schema(description = "the recipient of the request, used to reassign") private UUID recipientId; + @Schema(description = "Whether the feedback request has been denied") + private boolean denied = false; + + @Nullable + @Schema(description = "Reason for the request being denied") + private String reason; + } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationController.java b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationController.java new file mode 100644 index 000000000..3513ad3e1 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationController.java @@ -0,0 +1,45 @@ +package com.objectcomputing.checkins.services.notification; + +import com.objectcomputing.checkins.services.permissions.Permission; +import com.objectcomputing.checkins.services.permissions.RequiredPermission; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.validation.Validated; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; + +@Validated +@Controller("/services/notifications") +@ExecuteOn(TaskExecutors.BLOCKING) +@Secured(SecurityRule.IS_AUTHENTICATED) +@Tag(name = "notification") +public class NotificationController { + + private final NotificationService notificationService; + + public NotificationController(NotificationService notificationService) { + this.notificationService = notificationService; + } + + /** + * Send a notification to a user + * + * @param notificationDTO {@link NotificationDTO} containing the userId and message + * @return {@link HttpResponse} with status indicating success or error + */ + @Post("/send") + @RequiredPermission(Permission.CAN_SEND_NOTIFICATION) + public HttpResponse sendNotification(@Body @Valid @NotNull NotificationDTO notificationDTO) { + notificationService.sendNotification(notificationDTO.getUserId(), notificationDTO.getMessage()); + return HttpResponse.ok(); + } +} \ No newline at end of file diff --git a/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationDTO.java new file mode 100644 index 000000000..0ac78bdd0 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationDTO.java @@ -0,0 +1,33 @@ +package com.objectcomputing.checkins.services.notification; + +import io.micronaut.core.annotation.Introspected; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.util.UUID; + +@Introspected +public class NotificationDTO { + + @NotNull + private UUID userId; + + @NotBlank + private String message; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} \ No newline at end of file diff --git a/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationService.java b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationService.java new file mode 100644 index 000000000..754aca9e3 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationService.java @@ -0,0 +1,8 @@ +package com.objectcomputing.checkins.services.notification; + +import java.util.UUID; + +public interface NotificationService { + + void sendNotification(UUID userId, String message); +} \ No newline at end of file diff --git a/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationServiceImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationServiceImpl.java new file mode 100644 index 000000000..a9b892a6e --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationServiceImpl.java @@ -0,0 +1,65 @@ +package com.objectcomputing.checkins.services.notification; + +import io.micronaut.core.annotation.NonNull; +import com.objectcomputing.checkins.notifications.email.EmailSender; +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.objectcomputing.checkins.notifications.email.MailJetFactory; + + +import java.util.UUID; + +@Singleton +public class NotificationServiceImpl implements NotificationService { + + private static final Logger LOG = LoggerFactory.getLogger(NotificationServiceImpl.class); + + private final EmailSender emailSender; + private final MemberProfileServices memberProfileServices; + + @Inject + public NotificationServiceImpl( + @Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender, + MemberProfileServices memberProfileServices + ) { + this.emailSender = emailSender; + this.memberProfileServices = memberProfileServices; + } + + @Override + public void sendNotification(@NonNull UUID userId, @NonNull String message) { + MemberProfile user = memberProfileServices.getById(userId); + + if (user == null) { + throw new RuntimeException("User not found with ID: " + userId); + } + + String subject = "Feedback Request Denied"; + + MemberProfile denier = getCurrentDenier(); + String content = "

Feedback Request Denied

" + + "

Dear " + user.getFirstName() + " " + user.getLastName() + ",

" + + "

Your feedback request has been denied. The reason provided was:

" + + "

" + message + "

" + + "

Best Regards,

" + + "

" + denier.getFirstName() + " " + denier.getLastName() + "

"; + + + String fromName = denier.getFirstName() + " " + denier.getLastName(); + String fromAddress = denier.getWorkEmail(); + + emailSender.sendEmail(fromName, fromAddress, subject, content, user.getWorkEmail()); + } + + private MemberProfile getCurrentDenier() { + // This method should fetch the profile of the user who denied the request + // This could be passed as a parameter or retrieved from the session or context + // If not available, you might need to redesign how this information is passed to the service + throw new UnsupportedOperationException("Not implemented"); + } +} \ No newline at end of file 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 29e8add4c..29d4ca83d 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 @@ -8,12 +8,14 @@ @JsonSerialize(using = PermissionSerializer.class) public enum Permission { CAN_VIEW_FEEDBACK_REQUEST("View feedback requests", "Feedback"), + CAN_DENY_FEEDBACK_REQUEST("Deny feedback requests", "Feedback"), CAN_CREATE_FEEDBACK_REQUEST("Create feedback requests", "Feedback"), CAN_DELETE_FEEDBACK_REQUEST("Delete feedback requests", "Feedback"), CAN_CREATE_KUDOS("Create kudos", "Feedback"), CAN_ADMINISTER_KUDOS("Administer kudos", "Feedback"), CAN_VIEW_FEEDBACK_ANSWER("View feedback answers", "Feedback"), CAN_SEND_EMAIL("Send email", "Feedback"), + CAN_SEND_NOTIFICATION("Send notification", "Feedback"), CAN_DELETE_ORGANIZATION_MEMBERS("Delete organization members", "User Management"), CAN_CREATE_ORGANIZATION_MEMBERS("Create organization members", "User Management"), CAN_IMPERSONATE_MEMBERS("Impersonate organization members", "Security"), diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reviews/ReviewPeriodServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reviews/ReviewPeriodServicesImpl.java index 96db2a6a2..7a7a5b9ad 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reviews/ReviewPeriodServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reviews/ReviewPeriodServicesImpl.java @@ -356,7 +356,8 @@ private void createReviewRequest(ReviewPeriod period, LocalDate sendDate = LocalDate.now(); FeedbackRequest request = new FeedbackRequest( creatorId, revieweeId, reviewerId, templateId, sendDate, - dueDate, "sent", null, period.getId()); + dueDate, "sent", null, period.getId(), false, null + ); feedbackRequestServices.save(request); } catch(Exception ex) { LOG.error(ex.toString()); diff --git a/server/src/main/resources/db/common/V116__alter_feedback_requests_table.sql b/server/src/main/resources/db/common/V116__alter_feedback_requests_table.sql new file mode 100644 index 000000000..264f55612 --- /dev/null +++ b/server/src/main/resources/db/common/V116__alter_feedback_requests_table.sql @@ -0,0 +1,10 @@ +-- Migration to add 'denied' column to the feedback_requests table + +BEGIN; + +ALTER TABLE feedback_requests +ADD COLUMN denied BOOLEAN DEFAULT FALSE NOT NULL; + +COMMENT ON COLUMN feedback_requests.denied IS 'Indicates whether the feedback request has been denied.'; + +COMMIT; diff --git a/server/src/main/resources/db/common/V117__alter_feedback_requests_table.sql b/server/src/main/resources/db/common/V117__alter_feedback_requests_table.sql new file mode 100644 index 000000000..85403a4c2 --- /dev/null +++ b/server/src/main/resources/db/common/V117__alter_feedback_requests_table.sql @@ -0,0 +1,4 @@ +ALTER TABLE feedback_requests +ADD COLUMN reason VARCHAR(255); + +COMMENT ON COLUMN feedback_requests.reason IS 'Reason provided when the feedback request is denied.'; \ No newline at end of file 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 2aa8a6afc..3e7db6715 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 @@ -658,6 +658,11 @@ insert into role_permissions values ('e8a4fff8-e984-4e59-be84-a713c9fa8d23', 'CAN_DELETE_FEEDBACK_REQUEST'); +insert into role_permissions + (roleid, permission) +values + ('e8a4fff8-e984-4e59-be84-a713c9fa8d23', 'CAN_DENY_FEEDBACK_REQUEST'); + insert into role_permissions (roleid, permission) values @@ -990,6 +995,11 @@ insert into role_permissions values ('8bda2ae9-58c1-4843-a0d5-d0952621f9df', 'CAN_DELETE_FEEDBACK_REQUEST'); +insert into role_permissions + (roleid, permission) +values + ('8bda2ae9-58c1-4843-a0d5-d0952621f9df', 'CAN_DENY_FEEDBACK_REQUEST'); + insert into role_permissions (roleid, permission) values diff --git a/web-ui/src/api/feedback.js b/web-ui/src/api/feedback.js index 856618502..f1d39fa0b 100644 --- a/web-ui/src/api/feedback.js +++ b/web-ui/src/api/feedback.js @@ -1,3 +1,4 @@ +import { createDateStrForV6InputFromSections } from '@mui/x-date-pickers/internals'; import { resolve } from './api.js'; import { getFeedbackTemplateWithQuestions } from './feedbacktemplate.js'; @@ -118,6 +119,44 @@ export const cancelFeedbackRequest = async (feedbackRequest, cookie) => { }); }; +export const denyFeedbackRequest = async (requestId, reason, denier, creator, cookie) => { + console.log('Sending deny feedback request with data:', { + reason, + denier, + creator + }); + return resolve({ + method: 'POST', + url: `${feedbackRequestURL}/${requestId}/deny`, + data: { + reason: reason, + denier: denier, + creator: creator, + }, + headers: { + 'X-CSRF-Header': cookie, + Accept: 'application/json', + 'Content-Type': 'application/json;charset=UTF-8' + } + }); +}; + +export const sendNotification = async (userId, message, cookie) => { + return resolve({ + method: 'POST', + url: '/services/notifications/send', + data: { + userId: userId, + message: message + }, + headers: { + 'X-CSRF-Header': cookie, + Accept: 'applications/json', + 'Content-Type': 'application/json;charset=UTF-8' + } + }); +}; + export const deleteFeedbackRequestById = async (id, cookie) => { return resolve({ method: 'DELETE', @@ -126,6 +165,8 @@ export const deleteFeedbackRequestById = async (id, cookie) => { }); }; + + export const getFeedbackRequestById = async (id, cookie) => { return resolve({ url: `${feedbackRequestURL}/${id}`, diff --git a/web-ui/src/components/feedback_request_card/FeedbackRequestCard.css b/web-ui/src/components/feedback_request_card/FeedbackRequestCard.css index 549a98343..76b4cff65 100644 --- a/web-ui/src/components/feedback_request_card/FeedbackRequestCard.css +++ b/web-ui/src/components/feedback_request_card/FeedbackRequestCard.css @@ -12,6 +12,12 @@ } } +.denied-request { + opacity: 0.6; + pointer-events: none; + background-color: var(--checkins-palette-background-disabled); +} + .has-padding-top { padding-top: 1em; } @@ -63,6 +69,16 @@ width: 100%; } +.deny-feedback-button { + background-color: var(--checkins-palette-error); + color: white; + margin-top: 1em; + + &:hover { + background-color: darken(var(--checkins-palette-error), 10%); + } +} + .MuiFormControl-root { width: 20%; margin-right: 2em; @@ -80,4 +96,16 @@ .response-link { font-size: 0.7rem; } + + .deny-feedback-button { + font-size: 0.7rem; + padding: 6px 8px; + } } + +.denied-label { + color: var(--checkins-palette-error); + font-weight: bold; + margin-top: 10px; + display: block; +} \ No newline at end of file diff --git a/web-ui/src/components/feedback_request_card/FeedbackRequestCard.jsx b/web-ui/src/components/feedback_request_card/FeedbackRequestCard.jsx index ae439d4ef..0721c3584 100644 --- a/web-ui/src/components/feedback_request_card/FeedbackRequestCard.jsx +++ b/web-ui/src/components/feedback_request_card/FeedbackRequestCard.jsx @@ -91,7 +91,9 @@ const FeedbackRequestCard = ({ templateName, responses, sortType, - dateRange + dateRange, + onDeny, + isDenied }) => { const { state } = useContext(AppContext); const requesteeProfile = selectProfile(state, requesteeId); @@ -215,7 +217,7 @@ const FeedbackRequestCard = ({ }, [state, sortType, dateRange, responses, withinDateRange]); return ( -
+
diff --git a/web-ui/src/components/received_request_card/ReceivedRequestCard.css b/web-ui/src/components/received_request_card/ReceivedRequestCard.css index 563bf83ea..20d781d31 100644 --- a/web-ui/src/components/received_request_card/ReceivedRequestCard.css +++ b/web-ui/src/components/received_request_card/ReceivedRequestCard.css @@ -17,3 +17,8 @@ .ReceivedRequestCard-grayTypography { color: var(--checkins-palette-gray); } + +.denied-request { + background-color: #ffebee; + border-left: 5px solid #f44336; +} \ No newline at end of file diff --git a/web-ui/src/components/received_request_card/ReceivedRequestCard.jsx b/web-ui/src/components/received_request_card/ReceivedRequestCard.jsx index a5eae870d..41847bbeb 100644 --- a/web-ui/src/components/received_request_card/ReceivedRequestCard.jsx +++ b/web-ui/src/components/received_request_card/ReceivedRequestCard.jsx @@ -22,11 +22,10 @@ const classes = { const propTypes = { request: PropTypes.object.isRequired, - handleDenyClick: PropTypes.func.isRequired // handle deny function as a prop + handleDenyClick: PropTypes.func.isRequired }; -const ReceivedRequestCard = ({ request, handleDenyClick }) => { - console.log("Rendering ReceivedRequestCard for request:", request.id); +const ReceivedRequestCard = ({ request, handleDenyClick, isDenied }) => { const { state } = useContext(AppContext); @@ -70,6 +69,8 @@ const ReceivedRequestCard = ({ request, handleDenyClick }) => { } }; + + return (
@@ -112,6 +113,9 @@ const ReceivedRequestCard = ({ request, handleDenyClick }) => {
+ {isDenied && ( + Denied + )}
{request && @@ -129,7 +133,7 @@ const ReceivedRequestCard = ({ request, handleDenyClick }) => { { console.log("X icon clicked for request ID:", request.id); - handleDenyClick(request.id); // Call handleDenyClick with request ID + handleDenyClick(request.id); }}> @@ -142,6 +146,9 @@ const ReceivedRequestCard = ({ request, handleDenyClick }) => { ); }; -ReceivedRequestCard.propTypes = propTypes; +ReceivedRequestCard.propTypes = { + ...propTypes, + isDenied: PropTypes.bool +}; export default ReceivedRequestCard; \ No newline at end of file diff --git a/web-ui/src/components/view_feedback_responses/ViewFeedbackResponses.jsx b/web-ui/src/components/view_feedback_responses/ViewFeedbackResponses.jsx index 602d99842..5983c5a89 100644 --- a/web-ui/src/components/view_feedback_responses/ViewFeedbackResponses.jsx +++ b/web-ui/src/components/view_feedback_responses/ViewFeedbackResponses.jsx @@ -3,7 +3,7 @@ import { styled } from '@mui/material/styles'; import { Autocomplete, Avatar, Button, Checkbox, Chip, TextField, Typography, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; import FeedbackResponseCard from './feedback_response_card/FeedbackResponseCard'; import { getQuestionsAndAnswers } from '../../api/feedbackanswer'; -import { getFeedbackRequestById } from '../../api/feedback'; +import { getAnswerByRequestAndQuestionId, getFeedbackRequestById } from '../../api/feedback'; import queryString from 'query-string'; import { useLocation } from 'react-router-dom'; import { AppContext } from '../../context/AppContext'; @@ -81,7 +81,6 @@ const ViewFeedbackResponses = () => { const [currentResponder, setCurrentResponder] = useState(null); // Track the responder being denied const handleDenyClick = (responderId) => { - console.log(`Denial process initiated for responder: ${responderId}`); setCurrentResponder(responderId); // Track the current responder setDenialPopupOpen(true); }; @@ -91,10 +90,52 @@ const ViewFeedbackResponses = () => { console.log("Denial popup closed"); }; - const handleDenialSubmit = () => { + const handleDenialSubmit = async () => { console.log(`Denial reason for responder ${currentResponder}:`, denialReason); - setDenialPopupOpen(false); - // Here, you'd normally send the reason to the backend or handle it appropriately + + setQuestionsAndAnswers(prevState => + prevState.map(question => ({ + ...question, + answers: question.answers.filter(answer => answer.responder !== currentResponder) + })) + ); + + try { + const response = await fetch('/api/feedback/deny', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': csrf, + }, + body: JSON.stringify({ + requestId: query.request, + responderId: currentResponder, + denialReason: denialReason + }), + }); + + if (!response.ok) { + throw new Error('Failed to deny feedback request'); + } + + const result = await response.json(); + if (result.success) { + console.log('Feedback request denied successfully'); + } + + } catch (error) { + console.error("Error denying feedback request:", error); + window.snackDispatch({ + type: UPDATE_TOAST, + payload: { + severity: 'error', + toast: 'Failed to deny the feedback request' + } + }); + } finally { + setDenialPopupOpen(false); + setDenialReason(''); + } }; useEffect(() => { diff --git a/web-ui/src/pages/ReceivedRequestsPage.jsx b/web-ui/src/pages/ReceivedRequestsPage.jsx index 5828c5673..4f60558aa 100644 --- a/web-ui/src/pages/ReceivedRequestsPage.jsx +++ b/web-ui/src/pages/ReceivedRequestsPage.jsx @@ -13,7 +13,7 @@ import Typography from '@mui/material/Typography'; import { Search as SearchIcon } from '@mui/icons-material'; import { Collapse, InputAdornment, Dialog, DialogActions, DialogContent, DialogTitle, Button } from '@mui/material'; import ReceivedRequestCard from '../components/received_request_card/ReceivedRequestCard'; -import { getFeedbackRequestsByRecipient } from '../api/feedback'; +import { denyFeedbackRequest, getFeedbackRequestById, getFeedbackRequestsByRecipient } from '../api/feedback'; import './ReceivedRequestsPage.css'; import { UPDATE_TOAST } from '../context/actions'; import Divider from '@mui/material/Divider'; @@ -91,7 +91,6 @@ const ReceivedRequestsPage = () => { const [receivedRequestsExpanded, setReceivedRequestsExpanded] = useState(true); const [submittedRequestsExpanded, setSubmittedRequestsExpanded] = useState(false); - // State for denial dialog const [denialPopupOpen, setDenialPopupOpen] = useState(false); const [denialReason, setDenialReason] = useState(''); const [currentRequestId, setCurrentRequestId] = useState(null); @@ -109,14 +108,68 @@ const ReceivedRequestsPage = () => { console.log("Denial popup closed"); }; - const handleDenialSubmit = () => { - console.log("Denial reason for request ID:", currentRequestId, denialReason); - // Perform any necessary action for submitting the denial reason here - setDenialPopupOpen(false); - setDenialReason(''); - setCurrentRequestId(null); + const updateRequestAfterDenial = (requestId, reason) => { + setReceivedRequests(prevRequests => + prevRequests.filter(req => req.id !== requestId) + ); + console.log(`Request ${requestId} denied with reason: ${reason}`); }; + const handleDenialSubmit = async () => { + try { + const currentProfile = selectProfile(state, currentUserId); + const deniedRequest = receivedRequests.find(req => req.id === currentRequestId); + const creatorProfile = selectProfile(state, deniedRequest.creatorId); + + // await denyFeedbackRequest(currentRequestId, denialReason, csrf); + + const denier = { + id: currentProfile.id, + name: currentProfile.name + }; + + const creator = { + id: creatorProfile.id + }; + + + await denyFeedbackRequest( + currentRequestId, + denialReason, + denier, + creator, + csrf + ); + + const deniedRequestDetails = await getFeedbackRequestById(currentRequestId, csrf); + if (deniedRequestDetails.payload && deniedRequestDetails.payload.data) { + const creatorId = deniedRequest.payload.data.creatorId; + const notificationMessage = `Your feedback request for ${requesteeName} was denied. Reason: ${denialReason}`; + await sendNotification(creatorId, notificationMessage); + } + setReceivedRequests(prevRequests => + prevRequests.map(req => + req.id === currentRequestId ? { ...req, denied: true } : req + ) + ); + + window.snackDispatch({ + type: UPDATE_TOAST, + payload: { + severity: 'success', + toast: 'Feedback request denied and notification sent.' + } + }); + } catch (error) { + // Catch for error logging. Errors are handled elsewhere at time of writing. + } finally { + updateRequestAfterDenial(currentRequestId, denialReason); + setDenialPopupOpen(false); + setDenialReason(''); + setCurrentRequestId(null); + } +}; + useEffect(() => { const getAllFeedbackRequests = async () => { let res = await getFeedbackRequestsByRecipient(currentUserId, csrf); @@ -137,7 +190,7 @@ const ReceivedRequestsPage = () => { getAllFeedbackRequests().then(data => { if (data) { setCanceledRequests(data.filter(req => req.status === 'canceled')); - setReceivedRequests(data.filter(req => !req.submitDate && req.status !== 'canceled')); + setReceivedRequests(data.filter(req => !req.submitDate && req.status !== 'canceled' && !req.denied)); setSubmittedRequests(data.filter(req => req.submitDate && req.submitDate.length === 3 && req.status !== 'canceled')); setIsLoading(false); } @@ -280,7 +333,12 @@ const ReceivedRequestsPage = () => {
)} {filteredReceivedRequests.map(request => ( - + ))}
)} diff --git a/web-ui/src/pages/ViewFeedbackPage.jsx b/web-ui/src/pages/ViewFeedbackPage.jsx index ab72edf7e..5a7dbbd93 100644 --- a/web-ui/src/pages/ViewFeedbackPage.jsx +++ b/web-ui/src/pages/ViewFeedbackPage.jsx @@ -37,6 +37,8 @@ import { getFeedbackTemplate } from '../api/feedbacktemplate'; import SkeletonLoader from '../components/skeleton_loader/SkeletonLoader'; import { useQueryParameters } from '../helpers/query-parameters'; + + const PREFIX = 'ViewFeedbackPage'; const classes = { pageTitle: `${PREFIX}-pageTitle`, @@ -109,6 +111,7 @@ const ViewFeedbackPage = () => { const [sortValue, setSortValue] = useState(SortOption.SENT_DATE); const [dateRange, setDateRange] = useState(DateRange.THREE_MONTHS); const [includeAll, setIncludeAll] = useState(false); + const [deniedRequests, setDeniedRequests] = useState([]); useQueryParameters([ { @@ -139,13 +142,14 @@ const ViewFeedbackPage = () => { useEffect(() => { if (currentMembers && currentMembers.length > 0) { - isAdmin && includeAll - ? setTeamMembers( - currentMembers.filter(member => member?.id !== currentUserId) - ) - : includeAll - ? setTeamMembers(subordinates) - : setTeamMembers(myTeam); + const newTeamMembers = isAdmin && includeAll + ? currentMembers.filter(member => member?.id !== currentUserId) + : includeAll + ? subordinates + : myTeam; + if (JSON.stringify(teamMembers) !== JSON.stringify(newTeamMembers)) { + setTeamMembers(newTeamMembers); + } } }, [ isAdmin, @@ -153,7 +157,8 @@ const ViewFeedbackPage = () => { subordinates, currentMembers, myTeam, - currentUserId + currentUserId, + teamMembers ]); const toggleIncludeAll = useCallback(() => { @@ -162,6 +167,20 @@ const ViewFeedbackPage = () => { }, [includeAll, setIncludeAll]); useEffect(() => { + let mounted = true; + + const fetchData = async () => { + try { + const feedbackRequests = await getRequestAndTemplateInfo(currentUserId); + if (mounted) { + setFeedbackRequests(feedbackRequests); + isLoading.current = false; + } + } catch(error) { + console.error('Failed to fetch feedback requests:', error); + } + }; + const getFeedbackRequests = async creatorId => { if (csrf) { let res = await getFeedbackRequestsByCreator(creatorId, csrf); @@ -359,28 +378,47 @@ const ViewFeedbackPage = () => { return requestDate >= oldestDate; }; - return requestsToDisplay?.reduce((toDisplay, request) => { - if (request?.responses?.length > 0) { - if ( - request.responses.filter(response => isInRange(response.sendDate)) - .length > 0 - ) { - toDisplay.push( - - ); - } - } - return toDisplay; - }, []); - }, [searchText, sortValue, dateRange, feedbackRequests]); + const handleDenialOfFeedback = (requestId, reason) => { + setDeniedRequests(prev => [...prev, requestId]); + console.log(`Feedback denied for request ID: ${requestId} with reason: ${reason}`); + + + setFeedbackRequests(prevRequests => + prevRequests.map(request => ({ + ...request, + responses: request.responses.filter(response => response.id !== requestId) + })) + ); + }; + + + + return requestsToDisplay?.reduce((toDisplay, request) => { + if (request?.responses?.length > 0) { + request.responses = request.responses.filter(response => + isInRange(response.sendDate) && + !deniedRequests.includes(response.id) + ); + if (request.responses.length > 0) { + toDisplay.push( + + ); + } + } + return toDisplay; + }, []); +}, [searchText, sortValue, dateRange]); + return selectCanViewFeedbackRequestPermission(state) ? (
From 05143b527ca8e4985e4ba6d98bdf63aa363a2e4e Mon Sep 17 00:00:00 2001 From: thelenw Date: Mon, 11 Nov 2024 20:47:34 -0600 Subject: [PATCH 04/10] Updated files according to Michael's review, overcoming many errors to implement complete functionality to allow standard users the ability to deny feedback requests (as a new method with separate permission). The notification service is not yet complete, and some review facets need to be revisited. This is a valuable commit to save progress and clarify working state of long-outstanding PR draft. --- .../feedback_request/DTO/CreatorDTO.java | 27 --------- .../DTO/DenyFeedbackRequestDTO.java | 17 +++--- .../DTO/{DenierDTO.java => UserDTO.java} | 21 +++++-- .../FeedbackRequestController.java | 30 +++++----- .../FeedbackRequestDenialDTO.txt | 59 +++++++++++++++++++ .../FeedbackRequestServicesImpl.java | 4 ++ .../FeedbackRequestUpdateDTO.java | 1 + .../notification/NotificationServiceImpl.java | 11 ++-- web-ui/src/api/feedback.js | 5 +- 9 files changed, 115 insertions(+), 60 deletions(-) delete mode 100644 server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/CreatorDTO.java rename server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/{DenierDTO.java => UserDTO.java} (55%) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestDenialDTO.txt diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/CreatorDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/CreatorDTO.java deleted file mode 100644 index cd222a93e..000000000 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/CreatorDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.objectcomputing.checkins.services.feedback_request.DTO; - -import jakarta.validation.constraints.NotNull; -import java.util.UUID; - -public class CreatorDTO { - - @NotNull(message = "Creator ID cannot be null") - private UUID id; - - // Constructors - public CreatorDTO() {} - - public CreatorDTO(UUID id) { - this.id = id; - } - - // Getters - public UUID getId() { - return id; - } - - // Setters - public void setId(UUID id) { - this.id = id; - } -} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenyFeedbackRequestDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenyFeedbackRequestDTO.java index 5ea1a256a..088d8510b 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenyFeedbackRequestDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenyFeedbackRequestDTO.java @@ -1,9 +1,12 @@ package com.objectcomputing.checkins.services.feedback_request.DTO; +import io.micronaut.core.annotation.Introspected; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +@Introspected + public class DenyFeedbackRequestDTO { @NotBlank(message = "Reason cannot be blank") @@ -11,16 +14,16 @@ public class DenyFeedbackRequestDTO { @NotNull(message = "Denier cannot be null") @Valid - private DenierDTO denier; + private UserDTO denier; @NotNull(message = "Creator cannot be null") @Valid - private CreatorDTO creator; + private UserDTO creator; // Constructors public DenyFeedbackRequestDTO() {} - public DenyFeedbackRequestDTO(String reason, DenierDTO denier, CreatorDTO creator) { + public DenyFeedbackRequestDTO(String reason, UserDTO denier, UserDTO creator) { this.reason = reason; this.denier = denier; this.creator = creator; @@ -31,11 +34,11 @@ public String getReason() { return reason; } - public DenierDTO getDenier() { + public UserDTO getDenier() { return denier; } - public CreatorDTO getCreator() { + public UserDTO getCreator() { return creator; } @@ -44,11 +47,11 @@ public void setReason(String reason) { this.reason = reason; } - public void setDenier(DenierDTO denier) { + public void setDenier(UserDTO denier) { this.denier = denier; } - public void setCreator(CreatorDTO creator) { + public void setCreator(UserDTO creator) { this.creator = creator; } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenierDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/UserDTO.java similarity index 55% rename from server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenierDTO.java rename to server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/UserDTO.java index 46fb707a0..dc6823f65 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/DenierDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/DTO/UserDTO.java @@ -4,18 +4,29 @@ import jakarta.validation.constraints.NotNull; import java.util.UUID; -public class DenierDTO { +import io.micronaut.core.annotation.Introspected; - @NotNull(message = "Denier ID cannot be null") + +@Introspected + +public class UserDTO { + + @NotNull(message = "User ID cannot be null") private UUID id; - @NotBlank(message = "Denier name cannot be blank") + @NotBlank(message = "User name cannot be blank") private String name; // Constructors - public DenierDTO() {} + public UserDTO() {} + + // Constructor with only ID (for cases where only ID is required) + public UserDTO(UUID id) { + this.id = id; + } - public DenierDTO(UUID id, String name) { + // Constructor with ID and name (for cases where both ID and name are required) + public UserDTO(UUID id, String name) { this.id = id; this.name = name; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java index 409dacef2..bcddec824 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestController.java @@ -3,8 +3,7 @@ import com.objectcomputing.checkins.services.permissions.Permission; import com.objectcomputing.checkins.services.permissions.RequiredPermission; import com.objectcomputing.checkins.services.feedback_request.DTO.DenyFeedbackRequestDTO; -import com.objectcomputing.checkins.services.feedback_request.DTO.DenierDTO; -import com.objectcomputing.checkins.services.feedback_request.DTO.CreatorDTO; +import com.objectcomputing.checkins.services.feedback_request.DTO.UserDTO; import com.objectcomputing.checkins.services.notification.NotificationService; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.format.Format; @@ -137,21 +136,21 @@ public List findByValues( * @return {@link FeedbackRequestResponseDTO} with updated denial status */ @Post("/{id}/deny") -@RequiredPermission(Permission.CAN_DENY_FEEDBACK_REQUEST) -public HttpResponse denyFeedbackRequest( - @PathVariable("id") @NotNull UUID id, - @Body @Valid DenyFeedbackRequestDTO body -) { - FeedbackRequest feedbackRequest = feedbackReqServices.getById(id); - if (feedbackRequest == null) { - return HttpResponse.notFound(); + @RequiredPermission(Permission.CAN_DENY_FEEDBACK_REQUEST) + public HttpResponse denyFeedbackRequest( + @PathVariable("id") @NotNull UUID id, + @Body @Valid DenyFeedbackRequestDTO body + ) { + FeedbackRequest feedbackRequest = feedbackReqServices.getById(id); + if (feedbackRequest == null) { + return HttpResponse.notFound(); } String reason = body.getReason(); - DenierDTO denier = body.getDenier(); - CreatorDTO creator = body.getCreator(); + UserDTO denier = body.getDenier(); + UserDTO creator = body.getCreator(); - if (!feedbackRequest.isDenied() && reason != null && !reason.trim().isEmpty()) { + if (!feedbackRequest.isDenied() && reason != null && !reason.trim().isEmpty() && denier != null && creator != null) { FeedbackRequestUpdateDTO dto = new FeedbackRequestUpdateDTO(); dto.setId(feedbackRequest.getId()); dto.setDueDate(feedbackRequest.getDueDate()); @@ -171,9 +170,10 @@ public HttpResponse denyFeedbackRequest( ); return HttpResponse.ok(fromEntity(updatedFeedbackRequest)); - } + } else { + return HttpResponse.badRequest(); - return HttpResponse.ok(fromEntity(feedbackRequest)); + } } private FeedbackRequestResponseDTO fromEntity(FeedbackRequest feedbackRequest) { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestDenialDTO.txt b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestDenialDTO.txt new file mode 100644 index 000000000..bebcae8a9 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestDenialDTO.txt @@ -0,0 +1,59 @@ +package com.objectcomputing.checkins.services.feedback_request; + +import io.micronaut.core.annotation.Introspected; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import com.objectcomputing.checkins.services.feedback_request.DTO.UserDTO; + + +@Introspected + +public class FeedbackRequestDenialDTO { + + @NotBlank(message = "Reason cannot be blank") + private String reason; + + @NotNull(message = "Denier cannot be null") + @Valid + private UserDTO denier; + + @NotNull(message = "Creator cannot be null") + @Valid + private UserDTO creator; + + // Constructors + public FeedbackRequestDenialDTO() {} + + public FeedbackRequestDenialDTO(String reason, UserDTO denier, UserDTO creator) { + this.reason = reason; + this.denier = denier; + this.creator = creator; + } + + // Getters + public String getReason() { + return reason; + } + + public UserDTO getDenier() { + return denier; + } + + public UserDTO getCreator() { + return creator; + } + + // Setters + public void setReason(String reason) { + this.reason = reason; + } + + public void setDenier(UserDTO denier) { + this.denier = denier; + } + + public void setCreator(UserDTO creator) { + this.creator = creator; + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java index 0fa68e327..38bd9ddc0 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java @@ -215,6 +215,10 @@ public FeedbackRequest update(FeedbackRequestUpdateDTO feedbackRequestUpdateDTO) throw new BadArgException("Send date of feedback request must be before the due date."); } + if (feedbackRequest.isDenied() && (feedbackRequestUpdateDTO.getReason() == null || feedbackRequestUpdateDTO.getReason().trim().isEmpty())) { + throw new BadArgException("A reason must be provided for denying the request."); + } + FeedbackRequest storedRequest = feedbackReqRepository.update(feedbackRequest); MemberProfile reviewer = memberProfileServices.getById(storedRequest.getRecipientId()); MemberProfile requestee = memberProfileServices.getById(storedRequest.getRequesteeId()); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestUpdateDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestUpdateDTO.java index e1c0637ad..71f8df406 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestUpdateDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestUpdateDTO.java @@ -4,6 +4,7 @@ import io.micronaut.core.annotation.Nullable; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import com.objectcomputing.checkins.services.feedback_request.DTO.UserDTO; import lombok.Getter; import lombok.Setter; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationServiceImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationServiceImpl.java index a9b892a6e..2f8e5b9f1 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationServiceImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationServiceImpl.java @@ -4,6 +4,7 @@ import com.objectcomputing.checkins.notifications.email.EmailSender; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices; +import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.inject.Singleton; @@ -21,14 +22,17 @@ public class NotificationServiceImpl implements NotificationService { private final EmailSender emailSender; private final MemberProfileServices memberProfileServices; + private final CurrentUserServices currentUserServices; @Inject public NotificationServiceImpl( @Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender, - MemberProfileServices memberProfileServices + MemberProfileServices memberProfileServices, + CurrentUserServices currentUserServices ) { this.emailSender = emailSender; this.memberProfileServices = memberProfileServices; + this.currentUserServices = currentUserServices; } @Override @@ -57,9 +61,6 @@ public void sendNotification(@NonNull UUID userId, @NonNull String message) { } private MemberProfile getCurrentDenier() { - // This method should fetch the profile of the user who denied the request - // This could be passed as a parameter or retrieved from the session or context - // If not available, you might need to redesign how this information is passed to the service - throw new UnsupportedOperationException("Not implemented"); + return currentUserServices.getCurrentUser(); } } \ No newline at end of file diff --git a/web-ui/src/api/feedback.js b/web-ui/src/api/feedback.js index f1d39fa0b..4cf7bc81c 100644 --- a/web-ui/src/api/feedback.js +++ b/web-ui/src/api/feedback.js @@ -131,7 +131,10 @@ export const denyFeedbackRequest = async (requestId, reason, denier, creator, co data: { reason: reason, denier: denier, - creator: creator, + creator: { + id: creator.id, + name: "Anonymous" + } }, headers: { 'X-CSRF-Header': cookie, From ff42a4e5f58742b9a564544fa5ff91d51b3537db Mon Sep 17 00:00:00 2001 From: thelenw Date: Mon, 11 Nov 2024 22:11:05 -0600 Subject: [PATCH 05/10] Addressed Michael's review, updating Permissions. Migration versions and sendNotification function remain outstanding issues. --- .../feedback_request/FeedbackRequestServicesImpl.java | 10 ++++++++++ .../memberprofile/currentuser/CurrentUserServices.java | 4 ++++ .../currentuser/CurrentUserServicesImpl.java | 7 +++++++ .../services/notification/NotificationController.java | 2 +- .../checkins/services/permissions/Permission.java | 8 +++++--- .../src/main/resources/db/dev/R__Load_testing_data.sql | 5 +++++ .../checkins/services/fixture/PermissionFixture.java | 1 + 7 files changed, 33 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java index 38bd9ddc0..e6718126a 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java @@ -11,6 +11,7 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfileUtils; import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices; import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices; +import com.objectcomputing.checkins.services.permissions.Permission; import com.objectcomputing.checkins.services.reviews.ReviewAssignment; import com.objectcomputing.checkins.services.reviews.ReviewAssignmentRepository; import com.objectcomputing.checkins.services.reviews.ReviewPeriod; @@ -170,6 +171,15 @@ public FeedbackRequest update(FeedbackRequestUpdateDTO feedbackRequestUpdateDTO) throw new BadArgException("Cannot update feedback request that does not exist"); } + if (feedbackRequest.isDenied()) { + UUID currentUserId = currentUserServices.getCurrentUser().getId(); + if (!currentUserId.equals(originalFeedback.getRecipientId())) { + if (!currentUserServices.hasPermission(Permission.CAN_ADMINISTER_FEEDBACK_REQUESTS)) { + throw new PermissionException(NOT_AUTHORIZED_MSG); + } + } + } + validateMembers(originalFeedback); Set reviewAssignmentsSet = Set.of(); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/memberprofile/currentuser/CurrentUserServices.java b/server/src/main/java/com/objectcomputing/checkins/services/memberprofile/currentuser/CurrentUserServices.java index 98a3d154c..c5eb5c4c2 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/memberprofile/currentuser/CurrentUserServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/memberprofile/currentuser/CurrentUserServices.java @@ -2,6 +2,8 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.role.RoleType; +import com.objectcomputing.checkins.services.permissions.Permission; +import com.objectcomputing.checkins.exceptions.PermissionException; public interface CurrentUserServices { @@ -13,4 +15,6 @@ public interface CurrentUserServices { MemberProfile getCurrentUser(); + boolean hasPermission(Permission permission); + } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/memberprofile/currentuser/CurrentUserServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/memberprofile/currentuser/CurrentUserServicesImpl.java index ae063117c..f6f1a826a 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/memberprofile/currentuser/CurrentUserServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/memberprofile/currentuser/CurrentUserServicesImpl.java @@ -4,6 +4,7 @@ import com.objectcomputing.checkins.exceptions.NotFoundException; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.services.permissions.Permission; import com.objectcomputing.checkins.services.role.Role; import com.objectcomputing.checkins.services.role.RoleServices; import com.objectcomputing.checkins.services.role.RoleType; @@ -84,4 +85,10 @@ private MemberProfile saveNewUser(String firstName, String lastName, String work return createdMember; } + + @Override + public boolean hasPermission(Permission permission) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'hasPermission'"); + } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationController.java b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationController.java index 3513ad3e1..457ea8b31 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/notification/NotificationController.java @@ -37,7 +37,7 @@ public NotificationController(NotificationService notificationService) { * @return {@link HttpResponse} with status indicating success or error */ @Post("/send") - @RequiredPermission(Permission.CAN_SEND_NOTIFICATION) + @RequiredPermission(Permission.CAN_SEND_NOTIFICATIONS) public HttpResponse sendNotification(@Body @Valid @NotNull NotificationDTO notificationDTO) { notificationService.sendNotification(notificationDTO.getUserId(), notificationDTO.getMessage()); return HttpResponse.ok(); 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 29d4ca83d..9878cc90f 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 @@ -14,8 +14,8 @@ public enum Permission { CAN_CREATE_KUDOS("Create kudos", "Feedback"), CAN_ADMINISTER_KUDOS("Administer kudos", "Feedback"), CAN_VIEW_FEEDBACK_ANSWER("View feedback answers", "Feedback"), - CAN_SEND_EMAIL("Send email", "Feedback"), - CAN_SEND_NOTIFICATION("Send notification", "Feedback"), + CAN_SEND_EMAIL("Send email", "Notifications"), + CAN_SEND_NOTIFICATIONS("Send notifications", "Notifications"), CAN_DELETE_ORGANIZATION_MEMBERS("Delete organization members", "User Management"), CAN_CREATE_ORGANIZATION_MEMBERS("Create organization members", "User Management"), CAN_IMPERSONATE_MEMBERS("Impersonate organization members", "Security"), @@ -61,7 +61,9 @@ public enum Permission { CAN_ADMINISTER_VOLUNTEERING_ORGANIZATIONS("Update volunteering organizations", "Volunteering"), CAN_ADMINISTER_VOLUNTEERING_RELATIONSHIPS("Update volunteering relationships", "Volunteering"), CAN_ADMINISTER_VOLUNTEERING_EVENTS("Update volunteering events", "Volunteering"), - CAN_ADMINISTER_DOCUMENTATION("Administer documentation and role documentation", "Documentation"); + CAN_ADMINISTER_DOCUMENTATION("Administer documentation and role documentation", "Documentation"), + CAN_ADMINISTER_FEEDBACK_REQUESTS("Administer feedback requests", "Feedback"); + private final String description; private final String category; 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 3e7db6715..584fb52e5 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 @@ -898,6 +898,11 @@ insert into role_permissions values ('e8a4fff8-e984-4e59-be84-a713c9fa8d23', 'CAN_SEND_EMAIL'); +insert into role_permissions + (roleid, permission) +values + ('e8a4fff8-e984-4e59-be84-a713c9fa8d23', 'CAN_ADMINISTER_FEEDBACK_REQUESTS'); + -- PDL Permissions insert into role_permissions (roleid, permission) diff --git a/server/src/test/java/com/objectcomputing/checkins/services/fixture/PermissionFixture.java b/server/src/test/java/com/objectcomputing/checkins/services/fixture/PermissionFixture.java index cae3a2720..ae5ec6651 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/fixture/PermissionFixture.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/fixture/PermissionFixture.java @@ -94,6 +94,7 @@ public interface PermissionFixture extends RolePermissionFixture { Permission.CAN_ADMINISTER_VOLUNTEERING_RELATIONSHIPS, Permission.CAN_ADMINISTER_VOLUNTEERING_EVENTS, Permission.CAN_ADMINISTER_DOCUMENTATION, + Permission.CAN_ADMINISTER_FEEDBACK_REQUESTS, Permission.CAN_ADMINISTER_KUDOS, Permission.CAN_CREATE_KUDOS, Permission.CAN_IMPERSONATE_MEMBERS, From b03ef7e84117224be2d7c4aab539fd6280587346 Mon Sep 17 00:00:00 2001 From: thelenw Date: Tue, 12 Nov 2024 11:46:16 -0600 Subject: [PATCH 06/10] Reversioning migrations according to intermediate develop branch updates --- ...requests_table.sql => V119__alter_feedback_requests_table.sql} | 0 ...requests_table.sql => V120__alter_feedback_requests_table.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename server/src/main/resources/db/common/{V116__alter_feedback_requests_table.sql => V119__alter_feedback_requests_table.sql} (100%) rename server/src/main/resources/db/common/{V117__alter_feedback_requests_table.sql => V120__alter_feedback_requests_table.sql} (100%) diff --git a/server/src/main/resources/db/common/V116__alter_feedback_requests_table.sql b/server/src/main/resources/db/common/V119__alter_feedback_requests_table.sql similarity index 100% rename from server/src/main/resources/db/common/V116__alter_feedback_requests_table.sql rename to server/src/main/resources/db/common/V119__alter_feedback_requests_table.sql diff --git a/server/src/main/resources/db/common/V117__alter_feedback_requests_table.sql b/server/src/main/resources/db/common/V120__alter_feedback_requests_table.sql similarity index 100% rename from server/src/main/resources/db/common/V117__alter_feedback_requests_table.sql rename to server/src/main/resources/db/common/V120__alter_feedback_requests_table.sql From 25f2e4c0ff48f9fdea0c56243f238cebdbb493fc Mon Sep 17 00:00:00 2001 From: thelenw Date: Tue, 12 Nov 2024 11:57:47 -0600 Subject: [PATCH 07/10] Fixes for FeedbackRequestFixture to include 'Deny' functionality support --- .../fixture/FeedbackRequestFixture.java | 82 +++++++++++++++++-- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/server/src/test/java/com/objectcomputing/checkins/services/fixture/FeedbackRequestFixture.java b/server/src/test/java/com/objectcomputing/checkins/services/fixture/FeedbackRequestFixture.java index 8b1b1c6cc..f4c7df493 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/fixture/FeedbackRequestFixture.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/fixture/FeedbackRequestFixture.java @@ -1,15 +1,15 @@ package com.objectcomputing.checkins.services.fixture; + import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest; import com.objectcomputing.checkins.services.feedback_template.FeedbackTemplate; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.reviews.ReviewPeriod; import java.time.LocalDate; - -import java.util.UUID; import java.util.List; +import java.util.UUID; -public interface FeedbackRequestFixture extends RepositoryFixture, FeedbackTemplateFixture { +public interface FeedbackRequestFixture extends FeedbackTemplateFixture { /** * Creates a sample feedback request @@ -21,7 +21,19 @@ public interface FeedbackRequestFixture extends RepositoryFixture, FeedbackTempl */ default FeedbackRequest createSampleFeedbackRequest(MemberProfile creator, MemberProfile requestee, MemberProfile recipient, UUID templateId) { LocalDate testDate = LocalDate.of(2010, 10, 8); - return new FeedbackRequest(creator.getId(), requestee.getId(), recipient.getId(), templateId, testDate, null, "pending", null, null); + return new FeedbackRequest( + creator.getId(), + requestee.getId(), + recipient.getId(), + templateId, + testDate, + null, + "pending", + null, + null, + false, + null + ); } /** @@ -35,7 +47,19 @@ default FeedbackRequest createSampleFeedbackRequest(MemberProfile creator, Membe */ default FeedbackRequest createSampleFeedbackRequest(MemberProfile creator, MemberProfile requestee, MemberProfile recipient, UUID templateId, ReviewPeriod reviewPeriod) { LocalDate testDate = LocalDate.of(2010, 10, 8); - return new FeedbackRequest(creator.getId(), requestee.getId(), recipient.getId(), templateId, testDate, null, "pending", null, reviewPeriod.getId()); + return new FeedbackRequest( + creator.getId(), + requestee.getId(), + recipient.getId(), + templateId, + testDate, + null, + "pending", + null, + reviewPeriod.getId(), + false, + null + ); } /** @@ -48,7 +72,20 @@ default FeedbackRequest createSampleFeedbackRequest(MemberProfile creator, Membe */ default FeedbackRequest saveSampleFeedbackRequest(MemberProfile creator, MemberProfile requestee, MemberProfile recipient, UUID templateId) { LocalDate testDate = LocalDate.of(2010, 10, 8); - return getFeedbackRequestRepository().save(new FeedbackRequest(creator.getId(), requestee.getId(), recipient.getId(), templateId, testDate, null, "pending", null, null)); + FeedbackRequest feedbackRequest = new FeedbackRequest( + creator.getId(), + requestee.getId(), + recipient.getId(), + templateId, + testDate, + null, + "pending", + null, + null, + false, + null + ); + return getFeedbackRequestRepository().save(feedbackRequest); } /** @@ -57,16 +94,43 @@ default FeedbackRequest saveSampleFeedbackRequest(MemberProfile creator, MemberP * @param recipient The {@link MemberProfile} of the member giving feedback * @param requestee The {@link MemberProfile} of the requestee of the feedback request * @param templateId The UUID of the FeedbackTemplate + * @param reviewPeriod the {@link ReviewPeriod} that this feedback request is associated with * @return The saved {@link FeedbackRequest} */ default FeedbackRequest saveSampleFeedbackRequest(MemberProfile creator, MemberProfile requestee, MemberProfile recipient, UUID templateId, ReviewPeriod reviewPeriod) { LocalDate testDate = LocalDate.of(2010, 10, 8); - return getFeedbackRequestRepository().save(new FeedbackRequest(creator.getId(), requestee.getId(), recipient.getId(), templateId, testDate, null, "pending", null, reviewPeriod.getId())); + FeedbackRequest feedbackRequest = new FeedbackRequest( + creator.getId(), + requestee.getId(), + recipient.getId(), + templateId, + testDate, + null, + "pending", + null, + reviewPeriod.getId(), + false, + null + ); + return getFeedbackRequestRepository().save(feedbackRequest); } default FeedbackRequest saveSampleFeedbackRequestWithStatus(MemberProfile creator, MemberProfile requestee, MemberProfile recipient, UUID templateId, String status) { LocalDate testDate = LocalDate.of(2010, 10, 8); - return getFeedbackRequestRepository().save(new FeedbackRequest(creator.getId(), requestee.getId(), recipient.getId(), templateId, testDate, null, status, null, null)); + FeedbackRequest feedbackRequest = new FeedbackRequest( + creator.getId(), + requestee.getId(), + recipient.getId(), + templateId, + testDate, + null, + status, + null, + null, + false, + null + ); + return getFeedbackRequestRepository().save(feedbackRequest); } default MemberProfile createADefaultRecipient() { @@ -115,6 +179,6 @@ default FeedbackRequest saveFeedbackRequest(MemberProfile creator, MemberProfile default List getFeedbackRequests(MemberProfile recipient) { return getFeedbackRequestRepository() - .findByValues(null, null, recipient.getId().toString(), null, null, null); + .findByValues(null, null, recipient.getId().toString(), null, null, null); } } From 7d15622ed47a3f8f195cceef7c3bc1d8a6d0ef16 Mon Sep 17 00:00:00 2001 From: thelenw Date: Tue, 12 Nov 2024 15:12:59 -0600 Subject: [PATCH 08/10] Attempt to resolve build test failure --- .../feedback_response_card/FeedbackResponseCard.jsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/web-ui/src/components/view_feedback_responses/feedback_response_card/FeedbackResponseCard.jsx b/web-ui/src/components/view_feedback_responses/feedback_response_card/FeedbackResponseCard.jsx index 0b176081a..bf08e4891 100644 --- a/web-ui/src/components/view_feedback_responses/feedback_response_card/FeedbackResponseCard.jsx +++ b/web-ui/src/components/view_feedback_responses/feedback_response_card/FeedbackResponseCard.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; @@ -11,13 +11,6 @@ import Avatar from '@mui/material/Avatar'; import { getAvatarURL } from '../../../api/api.js'; import FeedbackAnswerInput from '../../feedback_answer_input/FeedbackAnswerInput'; -const propTypes = { - responderId: PropTypes.string.isRequired, - answer: PropTypes.string, // Allow answer to be null or undefined - inputType: PropTypes.string.isRequired, - sentiment: PropTypes.number -}; - const FeedbackResponseCard = props => { const { state } = useContext(AppContext); const userInfo = selectProfile(state, props.responderId); @@ -59,10 +52,10 @@ const FeedbackResponseCard = props => { FeedbackResponseCard.propTypes = { responderId: PropTypes.string.isRequired, - answer: PropTypes.string.isRequired, + answer: PropTypes.string, inputType: PropTypes.string.isRequired, sentiment: PropTypes.number, - handleDenyClick: PropTypes.func.isRequired + handleDenyClick: PropTypes.func }; export default FeedbackResponseCard; \ No newline at end of file From 40cea7655f40f6b19bf7d57468440547d4beea06 Mon Sep 17 00:00:00 2001 From: thelenw Date: Tue, 12 Nov 2024 21:07:58 -0600 Subject: [PATCH 09/10] Attempt to fix failing snapshot tests --- web-ui/package.json | 2 +- .../FeedbackRequestCard.test.jsx.snap | 2 +- .../FeedbackResponseCard.test.tsx.snap | 240 ++++++---- web-ui/yarn.lock | 452 +++++++----------- 4 files changed, 326 insertions(+), 370 deletions(-) diff --git a/web-ui/package.json b/web-ui/package.json index 00047a799..2c9fe490c 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -101,7 +101,7 @@ "typescript-eslint": "^7.7.1", "vite": "^5.2.14", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^1.6.0", + "vitest": "^2.1.4", "vitest-fetch-mock": "^0.2.2" } } diff --git a/web-ui/src/components/feedback_request_card/__snapshots__/FeedbackRequestCard.test.jsx.snap b/web-ui/src/components/feedback_request_card/__snapshots__/FeedbackRequestCard.test.jsx.snap index 75039b517..1be3c5e7f 100644 --- a/web-ui/src/components/feedback_request_card/__snapshots__/FeedbackRequestCard.test.jsx.snap +++ b/web-ui/src/components/feedback_request_card/__snapshots__/FeedbackRequestCard.test.jsx.snap @@ -3,7 +3,7 @@ exports[`renders correctly 1`] = `
@@ -95,44 +161,32 @@ exports[`FeedbackResponseCard > renders correctly for slider responses 1`] = ` exports[`FeedbackResponseCard > renders correctly for text responses 1`] = `
-
- Responder: - 01b7d769-9fa2-43ff-95c7-f3b950a27bf9 -
-

- Answer: - I love opossums. I have rehabilitated baby opossums for 25 years, and I intend to do so until my last day! -

- +
+
+        I love opossums. I have rehabilitated baby opossums for 25 years, and I intend to do so until my last day!
+      
diff --git a/web-ui/yarn.lock b/web-ui/yarn.lock index 097a1c5d2..47f1f674b 100644 --- a/web-ui/yarn.lock +++ b/web-ui/yarn.lock @@ -709,13 +709,6 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -1135,11 +1128,6 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz#f2feb149235a5dc1deb5439758f8871255e5a161" integrity sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ== -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - "@sindresorhus/merge-streams@^2.1.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz#719df7fb41766bc143369eaa0dd56d8dc87c9958" @@ -1686,49 +1674,64 @@ strip-literal "^2.0.0" test-exclude "^6.0.0" -"@vitest/expect@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30" - integrity sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ== +"@vitest/expect@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.4.tgz#48f4f53a01092a3bdc118cff245f79ef388bdd8e" + integrity sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA== dependencies: - "@vitest/spy" "1.6.0" - "@vitest/utils" "1.6.0" - chai "^4.3.10" + "@vitest/spy" "2.1.4" + "@vitest/utils" "2.1.4" + chai "^5.1.2" + tinyrainbow "^1.2.0" -"@vitest/runner@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.0.tgz#a6de49a96cb33b0e3ba0d9064a3e8d6ce2f08825" - integrity sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg== +"@vitest/mocker@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.4.tgz#0dc07edb9114f7f080a0181fbcdb16cd4a2d855d" + integrity sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ== dependencies: - "@vitest/utils" "1.6.0" - p-limit "^5.0.0" - pathe "^1.1.1" + "@vitest/spy" "2.1.4" + estree-walker "^3.0.3" + magic-string "^0.30.12" -"@vitest/snapshot@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.0.tgz#deb7e4498a5299c1198136f56e6e0f692e6af470" - integrity sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ== +"@vitest/pretty-format@2.1.4", "@vitest/pretty-format@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.4.tgz#fc31993bdc1ef5a6c1a4aa6844e7ba55658a4f9f" + integrity sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww== dependencies: - magic-string "^0.30.5" - pathe "^1.1.1" - pretty-format "^29.7.0" + tinyrainbow "^1.2.0" -"@vitest/spy@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.0.tgz#362cbd42ccdb03f1613798fde99799649516906d" - integrity sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw== +"@vitest/runner@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.4.tgz#f9346500bdd0be1c926daaac5d683bae87ceda2c" + integrity sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA== dependencies: - tinyspy "^2.2.0" + "@vitest/utils" "2.1.4" + pathe "^1.1.2" -"@vitest/utils@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1" - integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw== +"@vitest/snapshot@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.4.tgz#ef8c3f605fbc23a32773256d37d3fdfd9b23d353" + integrity sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q== dependencies: - diff-sequences "^29.6.3" - estree-walker "^3.0.3" - loupe "^2.3.7" - pretty-format "^29.7.0" + "@vitest/pretty-format" "2.1.4" + magic-string "^0.30.12" + pathe "^1.1.2" + +"@vitest/spy@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.4.tgz#4e90f9783437c5841a27c80f8fd84d7289a6100a" + integrity sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.4.tgz#6d67ac966647a21ce8bc497472ce230de3b64537" + integrity sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg== + dependencies: + "@vitest/pretty-format" "2.1.4" + loupe "^3.1.2" + tinyrainbow "^1.2.0" accepts@^1.3.5: version "1.3.8" @@ -1743,14 +1746,7 @@ acorn-jsx@^5.0.0, acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.3.2: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -acorn@^8.0.0, acorn@^8.11.0, acorn@^8.11.3, acorn@^8.12.0: +acorn@^8.0.0, acorn@^8.12.0: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -1939,10 +1935,10 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== astring@^1.8.0: version "1.9.0" @@ -2145,18 +2141,16 @@ ccount@^2.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chai@^4.3.10: - version "4.5.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" - integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== +chai@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" + integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== dependencies: - assertion-error "^1.1.0" - check-error "^1.0.3" - deep-eql "^4.1.3" - get-func-name "^2.0.2" - loupe "^2.3.6" - pathval "^1.1.1" - type-detect "^4.1.0" + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" chalk@^2.4.2: version "2.4.2" @@ -2208,12 +2202,10 @@ character-reference-invalid@^2.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== -check-error@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" - integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== - dependencies: - get-func-name "^2.0.2" +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== cheerio-select@^2.1.0: version "2.1.0" @@ -2360,11 +2352,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -confbox@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.7.tgz#ccfc0a2bcae36a84838e83a3b7f770fb17d6c579" - integrity sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA== - content-disposition@~0.5.2: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -2739,7 +2726,7 @@ dayjs@^1.11.11: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== -debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -2773,12 +2760,10 @@ decode-uri-component@^0.4.1: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5" integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== -deep-eql@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" - integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== - dependencies: - type-detect "^4.0.0" +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== deep-equal@^2.0.5: version "2.2.3" @@ -2892,11 +2877,6 @@ devlop@^1.0.0, devlop@^1.1.0: dependencies: dequal "^2.0.0" -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -3454,26 +3434,16 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" - integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^8.0.1" - human-signals "^5.0.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^4.1.0" - strip-final-newline "^3.0.0" - exenv@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== +expect-type@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75" + integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA== + extend@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -3653,11 +3623,6 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-func-name@^2.0.1, get-func-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" - integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== - get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" @@ -3686,11 +3651,6 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" -get-stream@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" - integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== - get-symbol-description@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" @@ -4175,11 +4135,6 @@ https-proxy-agent@^7.0.5: agent-base "^7.0.2" debug "4" -human-signals@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" - integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== - husky@^1.0.0-rc.14: version "1.3.1" resolved "https://registry.yarnpkg.com/husky/-/husky-1.3.1.tgz#26823e399300388ca2afff11cfa8a86b0033fae0" @@ -4519,11 +4474,6 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -4985,14 +4935,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -local-pkg@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.0.tgz#093d25a346bae59a99f80e75f6e9d36d7e8c925c" - integrity sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg== - dependencies: - mlly "^1.4.2" - pkg-types "^1.0.3" - locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -5040,12 +4982,10 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 dependencies: js-tokens "^3.0.0 || ^4.0.0" -loupe@^2.3.6, loupe@^2.3.7: - version "2.3.7" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" - integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== - dependencies: - get-func-name "^2.0.1" +loupe@^3.1.0, loupe@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== lru-cache@^10.2.0: version "10.4.3" @@ -5064,6 +5004,13 @@ lz-string@^1.5.0: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== +magic-string@^0.30.12: + version "0.30.12" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" + integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + magic-string@^0.30.5: version "0.30.11" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" @@ -5319,11 +5266,6 @@ mensch@^0.3.4: resolved "https://registry.yarnpkg.com/mensch/-/mensch-0.3.4.tgz#770f91b46cb16ea5b204ee735768c3f0c491fecd" integrity sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g== -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -5723,11 +5665,6 @@ mime@^2.4.6: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -6075,16 +6012,6 @@ mjml@^5.0.0-alpha.4: mjml-preset-core "5.0.0-alpha.4" mjml-validator "5.0.0-alpha.4" -mlly@^1.4.2, mlly@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.1.tgz#e0336429bb0731b6a8e887b438cbdae522c8f32f" - integrity sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA== - dependencies: - acorn "^8.11.3" - pathe "^1.1.2" - pkg-types "^1.1.1" - ufo "^1.5.3" - ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -6177,13 +6104,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" - integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== - dependencies: - path-key "^4.0.0" - nth-check@^2.0.0, nth-check@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" @@ -6271,13 +6191,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - only@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" @@ -6329,13 +6242,6 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" -p-limit@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" - integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== - dependencies: - yocto-queue "^1.0.0" - p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -6444,11 +6350,6 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -6484,15 +6385,15 @@ path-type@^5.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8" integrity sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg== -pathe@^1.1.1, pathe@^1.1.2: +pathe@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== -pathval@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" - integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== performance-now@^2.1.0: version "2.1.0" @@ -6530,15 +6431,6 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pkg-types@^1.0.3, pkg-types@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.2.0.tgz#d0268e894e93acff11a6279de147e83354ebd42d" - integrity sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA== - dependencies: - confbox "^0.1.7" - mlly "^1.7.1" - pathe "^1.1.2" - please-upgrade-node@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -6827,15 +6719,6 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - prism-react-renderer@^2.3.1: version "2.4.0" resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz#c5ea692029c2f8b3fd04f63662d04ffd4eaf10a0" @@ -6991,7 +6874,7 @@ react-is@^16.10.2, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.3.1: +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -7666,6 +7549,11 @@ std-env@^3.5.0: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +std-env@^3.7.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" + integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== + stop-iteration-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" @@ -7683,7 +7571,16 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7763,7 +7660,14 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -7782,11 +7686,6 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -7948,25 +7847,35 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinybench@^2.5.1: +tinybench@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== +tinyexec@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98" + integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ== + "tinymce@^7.0.0 || ^6.0.0 || ^5.5.1": version "7.3.0" resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.3.0.tgz#929b5d6d151b7d5e6f2efdaa802484834c42872c" integrity sha512-Ls4PgYlpk73XAxBSBqbVmSl8Mb3DuNfgF01GZ0lY6/MOEVRl3IL+VxC1Oe6165e8WqbqVsxO3Qj/PmoYNvQKGQ== -tinypool@^0.8.3: - version "0.8.4" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8" - integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ== +tinypool@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe" + integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA== -tinyspy@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1" - integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A== +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== to-fast-properties@^2.0.0: version "2.0.0" @@ -8044,11 +7953,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@^4.0.0, type-detect@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" - integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -8130,11 +8034,6 @@ typescript@^5.4.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== -ufo@^1.5.3: - version "1.5.4" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754" - integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ== - unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -8326,15 +8225,14 @@ victory-vendor@^36.6.8: d3-time "^3.0.0" d3-timer "^3.0.1" -vite-node@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f" - integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw== +vite-node@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.4.tgz#97ffb6de913fd8d42253afe441f9512e9dbdfd5c" + integrity sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg== dependencies: cac "^6.7.14" - debug "^4.3.4" - pathe "^1.1.1" - picocolors "^1.0.0" + debug "^4.3.7" + pathe "^1.1.2" vite "^5.0.0" vite-tsconfig-paths@^4.3.2: @@ -8364,31 +8262,31 @@ vitest-fetch-mock@^0.2.2: dependencies: cross-fetch "^3.0.6" -vitest@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.0.tgz#9d5ad4752a3c451be919e412c597126cffb9892f" - integrity sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA== - dependencies: - "@vitest/expect" "1.6.0" - "@vitest/runner" "1.6.0" - "@vitest/snapshot" "1.6.0" - "@vitest/spy" "1.6.0" - "@vitest/utils" "1.6.0" - acorn-walk "^8.3.2" - chai "^4.3.10" - debug "^4.3.4" - execa "^8.0.1" - local-pkg "^0.5.0" - magic-string "^0.30.5" - pathe "^1.1.1" - picocolors "^1.0.0" - std-env "^3.5.0" - strip-literal "^2.0.0" - tinybench "^2.5.1" - tinypool "^0.8.3" +vitest@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.4.tgz#ba8f4589fb639cf5a9e6af54781667312b3e8230" + integrity sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ== + dependencies: + "@vitest/expect" "2.1.4" + "@vitest/mocker" "2.1.4" + "@vitest/pretty-format" "^2.1.4" + "@vitest/runner" "2.1.4" + "@vitest/snapshot" "2.1.4" + "@vitest/spy" "2.1.4" + "@vitest/utils" "2.1.4" + chai "^5.1.2" + debug "^4.3.7" + expect-type "^1.1.0" + magic-string "^0.30.12" + pathe "^1.1.2" + std-env "^3.7.0" + tinybench "^2.9.0" + tinyexec "^0.3.1" + tinypool "^1.0.1" + tinyrainbow "^1.2.0" vite "^5.0.0" - vite-node "1.6.0" - why-is-node-running "^2.2.2" + vite-node "2.1.4" + why-is-node-running "^2.3.0" w3c-xmlserializer@^5.0.0: version "5.0.0" @@ -8533,7 +8431,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -why-is-node-running@^2.2.2: +why-is-node-running@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== @@ -8553,7 +8451,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -8571,6 +8469,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -8643,11 +8550,6 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -yocto-queue@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" - integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== - yoctocolors-cjs@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" From ddad64de029622e8447ebedc807e441928910a7f Mon Sep 17 00:00:00 2001 From: thelenw Date: Tue, 12 Nov 2024 22:18:48 -0600 Subject: [PATCH 10/10] Reverted vitest version to match tests --- web-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-ui/package.json b/web-ui/package.json index 2c9fe490c..00047a799 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -101,7 +101,7 @@ "typescript-eslint": "^7.7.1", "vite": "^5.2.14", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.1.4", + "vitest": "^1.6.0", "vitest-fetch-mock": "^0.2.2" } }