diff --git a/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponse.java b/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponse.java index 9c22589300..1b59be5c40 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponse.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponse.java @@ -52,7 +52,7 @@ public class PulseResponse { @Column(name="teammemberid") @TypeDef(type=DataType.STRING) - @NotNull + @Nullable @Schema(description = "id of the teamMember this entry is associated with") private UUID teamMemberId; @@ -77,7 +77,7 @@ public class PulseResponse { protected PulseResponse() { } - public PulseResponse(UUID id, Integer internalScore, Integer externalScore, LocalDate submissionDate, UUID teamMemberId, String internalFeelings, String externalFeelings) { + public PulseResponse(UUID id, Integer internalScore, Integer externalScore, LocalDate submissionDate, @Nullable UUID teamMemberId, String internalFeelings, String externalFeelings) { this.id = id; this.internalScore = internalScore; this.externalScore = externalScore; @@ -88,7 +88,7 @@ public PulseResponse(UUID id, Integer internalScore, Integer externalScore, Loca } public PulseResponse(Integer internalScore, Integer externalScore, LocalDate submissionDate, UUID teamMemberId, String internalFeelings, String externalFeelings) { - this(null,internalScore, externalScore, submissionDate, teamMemberId, internalFeelings, externalFeelings); + this(null, internalScore, externalScore, submissionDate, teamMemberId, internalFeelings, externalFeelings); } public UUID getId() { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseCreateDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseCreateDTO.java index aecb35e607..a5a289afa8 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseCreateDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseCreateDTO.java @@ -27,7 +27,7 @@ public class PulseResponseCreateDTO { @Schema(description = "date for submissionDate") private LocalDate submissionDate; - @NotNull + @Nullable @Schema(description = "id of the associated member") private UUID teamMemberId; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseServicesImpl.java index 50004da289..0cd2ee2177 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseServicesImpl.java @@ -55,11 +55,14 @@ public PulseResponse save(PulseResponse pulseResponse) { LocalDate pulseSubDate = pulseResponse.getSubmissionDate(); if (pulseResponse.getId() != null) { throw new BadArgException(String.format("Found unexpected id for pulseresponse %s", pulseResponse.getId())); - } else if (memberRepo.findById(memberId).isEmpty()) { + } else if (memberId != null && + memberRepo.findById(memberId).isEmpty()) { throw new BadArgException(String.format("Member %s doesn't exists", memberId)); } else if (pulseSubDate.isBefore(LocalDate.EPOCH) || pulseSubDate.isAfter(LocalDate.MAX)) { throw new BadArgException(String.format("Invalid date for pulseresponse submission date %s", memberId)); - } else if (!currentUserId.equals(memberId) && !isSubordinateTo(memberId, currentUserId)) { + } else if (memberId != null && + !currentUserId.equals(memberId) && + !isSubordinateTo(memberId, currentUserId)) { throw new BadArgException(String.format("User %s does not have permission to create pulse response for user %s", currentUserId, memberId)); } pulseResponseRet = pulseResponseRepo.save(pulseResponse); @@ -94,7 +97,7 @@ public PulseResponse update(PulseResponse pulseResponse) { } else if (memberRepo.findById(memberId).isEmpty()) { throw new BadArgException(String.format("Member %s doesn't exist", memberId)); } else if (memberId == null) { - throw new BadArgException(String.format("Invalid pulseresponse %s", pulseResponse)); + throw new BadArgException("Cannot update anonymous pulse response"); } else if (pulseSubDate.isBefore(LocalDate.EPOCH) || pulseSubDate.isAfter(LocalDate.MAX)) { throw new BadArgException(String.format("Invalid date for pulseresponse submission date %s", memberId)); } else if (!currentUserId.equals(memberId) && !isSubordinateTo(memberId, currentUserId)) { @@ -191,4 +194,4 @@ public void sendPulseLowScoreEmail(PulseResponse pulseResponse) { emailSender.sendEmail(null, null, subject, bodyBuilder.toString(), recipients.toArray(new String[0])); } } -} \ No newline at end of file +} diff --git a/server/src/test/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseControllerTest.java b/server/src/test/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseControllerTest.java index 777f61bac6..912038b5f5 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseControllerTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseControllerTest.java @@ -76,8 +76,12 @@ void testCreateAnInvalidPulseResponse() { JsonNode body = responseException.getResponse().getBody(JsonNode.class).orElse(null); JsonNode errors = Objects.requireNonNull(body).get("_embedded").get("errors"); JsonNode href = Objects.requireNonNull(body).get("_links").get("self").get("href"); - List errorList = Stream.of(errors.get(0).get("message").asText(), errors.get(1).get("message").asText(), errors.get(2).get("message").asText()).sorted().collect(Collectors.toList()); - assertEquals(3, errorList.size()); + List errorList = Stream.of( + errors.get(0).get("message").asText(), + errors.get(1).get("message").asText() + ).sorted().collect(Collectors.toList()); + + assertEquals(2, errorList.size()); assertEquals(request.getPath(), href.asText()); assertEquals(HttpStatus.BAD_REQUEST, responseException.getStatus()); } @@ -523,4 +527,4 @@ private MemberProfile profile(String key) { private UUID id(String key) { return profile(key).getId(); } -} \ No newline at end of file +} diff --git a/server/src/test/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseCreateDTOTest.java b/server/src/test/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseCreateDTOTest.java index 7b964d776c..761a41a495 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseCreateDTOTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/pulseresponse/PulseResponseCreateDTOTest.java @@ -35,7 +35,7 @@ void testConstraintViolation() { PulseResponseCreateDTO dto = new PulseResponseCreateDTO(); Set> violations = validator.validate(dto); - assertEquals(3, violations.size()); + assertEquals(2, violations.size()); for (ConstraintViolation violation : violations) { assertEquals("must not be null", violation.getMessage()); } diff --git a/web-ui/package.json b/web-ui/package.json index 32ee428d5e..49d35345a6 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -28,6 +28,7 @@ "fuse.js": "^6.4.6", "html-react-parser": "^5.1.12", "isomorphic-fetch": "^3.0.0", + "js-cookie": "^3.0.5", "js-file-download": "^0.4.12", "lodash": "^4.17.21", "markdown-builder": "^0.9.0", diff --git a/web-ui/src/pages/PulsePage.css b/web-ui/src/pages/PulsePage.css index c2f22a657c..923924b421 100644 --- a/web-ui/src/pages/PulsePage.css +++ b/web-ui/src/pages/PulsePage.css @@ -12,3 +12,9 @@ text-align: center; } } + +.submit-row { + align-items: center; + display: flex; + margin-top: 2rem; /* This is the default top margin for Buttons */ +} diff --git a/web-ui/src/pages/PulsePage.jsx b/web-ui/src/pages/PulsePage.jsx index 81f821d249..ef6877f019 100644 --- a/web-ui/src/pages/PulsePage.jsx +++ b/web-ui/src/pages/PulsePage.jsx @@ -1,8 +1,9 @@ +import Cookies from 'js-cookie'; import { format } from 'date-fns'; import React, { useContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { Button, Typography } from '@mui/material'; -import { resolve } from '../api/api.js'; +import { Button, Checkbox, Typography } from '@mui/material'; +import { downloadData, initiate } from '../api/generic.js'; import Pulse from '../components/pulse/Pulse.jsx'; import { AppContext } from '../context/AppContext'; import { selectCsrfToken, selectCurrentUser } from '../context/selectors'; @@ -15,7 +16,6 @@ const PulsePage = () => { const { state } = useContext(AppContext); const currentUser = selectCurrentUser(state); const csrf = selectCsrfToken(state); - const history = useHistory(); const [externalComment, setExternalComment] = useState(''); const [externalScore, setExternalScore] = useState(center); @@ -23,9 +23,19 @@ const PulsePage = () => { const [internalScore, setInternalScore] = useState(center); const [pulse, setPulse] = useState(null); const [submittedToday, setSubmittedToday] = useState(false); + const [submitAnonymously, setSubmitAnonymously] = useState(false); + const today = format(new Date(), 'yyyy-MM-dd'); + const cookieName = "pulse_submitted_anonymously"; + const pulseURL = '/services/pulse-responses'; useEffect(() => { + const submitted = Cookies.get(cookieName); + if (submitted) { + setSubmittedToday(true); + return; + } + if (!pulse) return; const now = new Date(); @@ -52,19 +62,8 @@ const PulsePage = () => { dateTo: today, teamMemberId: currentUser.id }; - const queryString = Object.entries(query) - .map(([key, value]) => `${key}=${value}`) - .join('&'); - - const res = await resolve({ - method: 'GET', - url: `/services/pulse-responses?${queryString}`, - headers: { - 'X-CSRF-Header': csrf, - Accept: 'application/json', - 'Content-Type': 'application/json;charset=UTF-8' - } - }); + + const res = await downloadData(pulseURL, csrf, query); if (res.error) return; // Sort pulse responses by date, latest to earliest @@ -97,22 +96,15 @@ const PulsePage = () => { internalScore: internalScore + 1, // converts to 1-based submissionDate: today, updatedDate: today, - teamMemberId: myId + teamMemberId: submitAnonymously ? null : myId, }; - const res = await resolve({ - method: 'POST', - url: '/services/pulse-responses', - headers: { - 'X-CSRF-Header': csrf, - Accept: 'application/json', - 'Content-Type': 'application/json;charset=UTF-8' - }, - data - }); + const res = await initiate(pulseURL, csrf, data); if (res.error) return; - // Refresh browser to show that pulses where already submitted today. - history.go(0); + setSubmittedToday(true); + if (submitAnonymously) { + Cookies.set(cookieName, 'true', { expires: 1 }); + } }; return ( @@ -141,9 +133,25 @@ const PulsePage = () => { setScore={setExternalScore} title="How are you feeling about life outside of work?" /> - +
+ +
+ +
)}
diff --git a/web-ui/src/pages/PulseReportPage.jsx b/web-ui/src/pages/PulseReportPage.jsx index 385db37ddf..4fc4415ca5 100644 --- a/web-ui/src/pages/PulseReportPage.jsx +++ b/web-ui/src/pages/PulseReportPage.jsx @@ -161,7 +161,7 @@ const PulseReportPage = () => { for (const pulse of pulses) { const memberId = pulse.teamMemberId; - if (!teamMemberIds.includes(memberId)) continue; + if (memberId && !teamMemberIds.includes(memberId)) continue; const { externalScore, internalScore, submissionDate } = pulse; const [year, month, day] = submissionDate; @@ -181,18 +181,21 @@ const PulseReportPage = () => { frequencies[internalScore - 1].internal++; frequencies[externalScore - 1].external++; - const member = memberMap[memberId]; - const { supervisorid } = member; - const memberIdToUse = managerMode ? supervisorid : memberId; - - /* For debugging ... - if (supervisorid) { - const supervisor = memberMap[supervisorid]; - console.log(`The supervisor of ${member.name} is ${supervisor.name}`); - } else { - console.log(`${member.name} has no supervisor`); + let memberIdToUse; + if (memberId) { + const member = memberMap[memberId]; + const { supervisorid } = member; + memberIdToUse = managerMode ? supervisorid : memberId; + + /* For debugging ... + if (supervisorid) { + const supervisor = memberMap[supervisorid]; + console.log(`The supervisor of ${member.name} is ${supervisor.name}`); + } else { + console.log(`${member.name} has no supervisor`); + } + */ } - */ // When in manager mode, if the member // doesn't have a supervisor then skip this data. diff --git a/web-ui/src/pages/__snapshots__/PulsePage.test.jsx.snap b/web-ui/src/pages/__snapshots__/PulsePage.test.jsx.snap index 4d308c868a..f433cc329b 100644 --- a/web-ui/src/pages/__snapshots__/PulsePage.test.jsx.snap +++ b/web-ui/src/pages/__snapshots__/PulsePage.test.jsx.snap @@ -455,16 +455,48 @@ exports[`renders correctly 1`] = ` - +
- + +
`; diff --git a/web-ui/yarn.lock b/web-ui/yarn.lock index 51d7dff66c..caac92af81 100644 --- a/web-ui/yarn.lock +++ b/web-ui/yarn.lock @@ -4627,6 +4627,11 @@ jest-fetch-mock@^3.0.3: cross-fetch "^3.0.4" promise-polyfill "^8.1.3" +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + js-file-download@^0.4.12: version "0.4.12" resolved "https://registry.yarnpkg.com/js-file-download/-/js-file-download-0.4.12.tgz#10c70ef362559a5b23cdbdc3bd6f399c3d91d821"