From 814225a339869220e02abd91af3f8c62cce4106e Mon Sep 17 00:00:00 2001 From: Philipp Berger <philipp.berger@nexenio.com> Date: Tue, 14 Sep 2021 20:31:07 +0200 Subject: [PATCH] chore: release v2.0.1 --- .prettierrc | 14 ++ CHANGELOG.md | 3 + e2e/package.json | 2 +- package.json | 95 ++++++------ scripts/updatePackageJsons.sh | 11 ++ services/backend/.eslintrc.js | 2 + services/backend/package.json | 2 +- services/backend/src/constants/riskLevels.ts | 4 + .../src/routes/v4/riskLevels.schemas.ts | 6 +- services/backend/src/routes/v4/riskLevels.ts | 70 +-------- .../utils/notifications/notificationsV4.ts | 2 +- services/contact-form/package.json | 2 +- services/health-department/package.json | 2 +- .../NotificationTrigger.react.js | 35 ++++- .../GuestList/GuestList.react.js | 24 +-- .../GuestListTable/GuestListTable.react.js | 6 +- .../ContactPersons/Notify/Notify.helper.js | 11 -- .../ContactPersons/Notify/Notify.react.js | 6 +- .../App/modals/ModalArea/ModalArea.react.js | 2 +- .../NotificationModal.helper.js | 14 +- .../NotificationModal.react.js | 138 ++++++++---------- .../NotificationModal.styled.js | 8 - .../src/constants/deviceTypes.js | 4 + .../health-department/src/messages/de.json | 8 +- .../health-department/src/messages/en.json | 14 +- services/health-department/src/network/api.js | 9 -- .../health-department/src/reducers/modals.js | 8 +- services/locations/package.json | 2 +- services/scanner/package.json | 2 +- services/webapp/package.json | 2 +- 30 files changed, 232 insertions(+), 276 deletions(-) create mode 100644 .prettierrc create mode 100755 scripts/updatePackageJsons.sh create mode 100644 services/backend/src/constants/riskLevels.ts delete mode 100644 services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.helper.js create mode 100644 services/health-department/src/constants/deviceTypes.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a2c7a12 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,14 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "avoid", + "requirePragma": false, + "insertPragma": false, + "proseWrap": "always" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 31046c7..6b7ae3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### 2.0.1 (2021-09-14) +* **backend:** feat: allow notification of individual users with level 2 notifications + ### 2.0.0 (2021-09-10) * **backend:** fix: strict JWT schema check for signing app * **backend:** fix: netmask of ipv6 addresses diff --git a/e2e/package.json b/e2e/package.json index 6496b3b..4f5b8fa 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "e2e", - "version": "2.0.0", + "version": "2.0.1", "main": "index.js", "private": true, "engines": { diff --git a/package.json b/package.json index 2ecaba7..15b9d38 100644 --- a/package.json +++ b/package.json @@ -1,49 +1,50 @@ { - "name": "@lucaapp/web", - "version": "2.0.0", - "private": true, - "license": "Apache-2.0", - "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", - "scripts": { - "latest": ". ./scripts/getGITEnvironmentVariables.sh && docker-compose -f docker-compose.yml", - "prod": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=prod docker-compose -f docker-compose.yml", - "dev": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=dev docker-compose -f docker-compose.yml -f docker-compose.dev.yml", - "test": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=test docker-compose -f docker-compose.yml -f docker-compose.test.yml", - "all": "./scripts/yarnAll.sh", - "prepare": "git lfs install && husky install && yarn generate:certs", - "generate:certs": "./scripts/generateCertificates.sh", - "lint:backend": "npm run lint --prefix services/backend", - "lint:contact-form": "npm run lint --prefix services/contact-form", - "lint:health-department": "npm run lint --prefix services/health-department", - "lint:locations": "npm run lint --prefix services/locations", - "lint:scanner": "npm run lint --prefix services/scanner", - "lint:webapp": "npm run lint --prefix services/webapp" - }, - "lint-staged": { - "services/backend/**/*.{js,jsx}": [ - "npm run --silent lint:backend" - ], - "services/contact-form/**/*.{js,jsx}": [ - "npm run --silent lint:contact-form" - ], - "services/health-department/**/*.{js,jsx}": [ - "npm run --silent lint:health-department" - ], - "services/locations/**/*.{js,jsx}": [ - "npm run --silent lint:locations" - ], - "services/scanner/**/*.{js,jsx}": [ - "npm run --silent lint:scanner" - ], - "services/webapp/**/*.{js,jsx}": [ - "npm run --silent lint:webapp" - ] - }, - "devDependencies": { - "husky": "6.0.0", - "lint-staged": "11.0.0" - }, - "dependencies": { - "@types/lodash": "4.14.172" - } + "version": "2.0.1", + "name": "@lucaapp/web", + "private": true, + "license": "Apache-2.0", + "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", + "scripts": { + "latest": ". ./scripts/getGITEnvironmentVariables.sh && docker-compose -f docker-compose.yml", + "prod": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=prod docker-compose -f docker-compose.yml", + "dev": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=dev docker-compose -f docker-compose.yml -f docker-compose.dev.yml", + "test": ". ./scripts/getGITEnvironmentVariables.sh && IMAGE_TAG=test docker-compose -f docker-compose.yml -f docker-compose.test.yml", + "all": "./scripts/yarnAll.sh", + "prepare": "git lfs install && husky install && yarn generate:certs", + "update:version": "./scripts/updatePackageJsons.sh", + "generate:certs": "./scripts/generateCertificates.sh", + "lint:backend": "npm run lint --prefix services/backend", + "lint:contact-form": "npm run lint --prefix services/contact-form", + "lint:health-department": "npm run lint --prefix services/health-department", + "lint:locations": "npm run lint --prefix services/locations", + "lint:scanner": "npm run lint --prefix services/scanner", + "lint:webapp": "npm run lint --prefix services/webapp" + }, + "lint-staged": { + "services/backend/**/*.{js,jsx}": [ + "npm run --silent lint:backend" + ], + "services/contact-form/**/*.{js,jsx}": [ + "npm run --silent lint:contact-form" + ], + "services/health-department/**/*.{js,jsx}": [ + "npm run --silent lint:health-department" + ], + "services/locations/**/*.{js,jsx}": [ + "npm run --silent lint:locations" + ], + "services/scanner/**/*.{js,jsx}": [ + "npm run --silent lint:scanner" + ], + "services/webapp/**/*.{js,jsx}": [ + "npm run --silent lint:webapp" + ] + }, + "devDependencies": { + "husky": "6.0.0", + "lint-staged": "11.0.0" + }, + "dependencies": { + "@types/lodash": "4.14.172" + } } diff --git a/scripts/updatePackageJsons.sh b/scripts/updatePackageJsons.sh new file mode 100755 index 0000000..d0dcf2a --- /dev/null +++ b/scripts/updatePackageJsons.sh @@ -0,0 +1,11 @@ +#!/bin/bash +echo "Updating package.json files to version $1" + +npx json -I -f package.json -e "this.version=\"$1\"" +npx json -I -f e2e/package.json -e "this.version=\"$1\"" +npx json -I -f services/backend/package.json -e "this.version=\"$1\"" +npx json -I -f services/contact-form/package.json -e "this.version=\"$1\"" +npx json -I -f services/health-department/package.json -e "this.version=\"$1\"" +npx json -I -f services/locations/package.json -e "this.version=\"$1\"" +npx json -I -f services/scanner/package.json -e "this.version=\"$1\"" +npx json -I -f services/webapp/package.json -e "this.version=\"$1\"" \ No newline at end of file diff --git a/services/backend/.eslintrc.js b/services/backend/.eslintrc.js index dfd2dae..c28c818 100644 --- a/services/backend/.eslintrc.js +++ b/services/backend/.eslintrc.js @@ -27,6 +27,8 @@ module.exports = { ecmaVersion: 2020, }, rules: { + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': ['error'], 'max-lines': [2, { max: 250, skipBlankLines: true, skipComments: true }], complexity: 2, 'no-await-in-loop': 0, diff --git a/services/backend/package.json b/services/backend/package.json index 927b022..5c5f072 100644 --- a/services/backend/package.json +++ b/services/backend/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/backend", - "version": "2.0.0", + "version": "2.0.1", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/backend/src/constants/riskLevels.ts b/services/backend/src/constants/riskLevels.ts new file mode 100644 index 0000000..aa12095 --- /dev/null +++ b/services/backend/src/constants/riskLevels.ts @@ -0,0 +1,4 @@ +export enum RiskLevel { + RISK_LEVEL_2 = 2, + RISK_LEVEL_3 = 3, +} diff --git a/services/backend/src/routes/v4/riskLevels.schemas.ts b/services/backend/src/routes/v4/riskLevels.schemas.ts index d251b8d..7a8d9d5 100644 --- a/services/backend/src/routes/v4/riskLevels.schemas.ts +++ b/services/backend/src/routes/v4/riskLevels.schemas.ts @@ -1,8 +1,5 @@ import { z } from 'utils/validation'; - -export const addRiskLevelSchema = z.object({ - locationTransferId: z.uuid(), -}); +import { RiskLevel } from 'constants/riskLevels'; export const getRiskLevelParameterSchema = z.object({ locationTransferId: z.uuid(), @@ -11,4 +8,5 @@ export const getRiskLevelParameterSchema = z.object({ export const addRiskLevelTracesSchema = z.object({ locationTransferId: z.uuid(), traceIds: z.array(z.traceId()), + riskLevel: z.nativeEnum(RiskLevel), }); diff --git a/services/backend/src/routes/v4/riskLevels.ts b/services/backend/src/routes/v4/riskLevels.ts index 98f94fc..22f0f70 100644 --- a/services/backend/src/routes/v4/riskLevels.ts +++ b/services/backend/src/routes/v4/riskLevels.ts @@ -8,64 +8,12 @@ import { } from 'middlewares/validateSchema'; import { requireHealthDepartmentEmployee } from 'middlewares/requireUser'; import { - addRiskLevelSchema, getRiskLevelParameterSchema, addRiskLevelTracesSchema, } from './riskLevels.schemas'; -const RISK_LEVEL_2 = 2; -const RISK_LEVEL_3 = 3; - const router = Router(); -router.post( - '/', - requireHealthDepartmentEmployee, - validateSchema(addRiskLevelSchema), - async (request, response) => { - const user = request.user as IHealthDepartmentEmployee; - - const locationTransfer = await database.LocationTransfer.findOne({ - where: { - uuid: request.body.locationTransferId, - departmentId: user.departmentId, - }, - }); - - if (!locationTransfer) return response.sendStatus(status.NOT_FOUND); - - const locationTransferTraces = await database.LocationTransferTrace.findAll( - { - where: { locationTransferId: locationTransfer.uuid }, - include: { - model: database.RiskLevel, - }, - } - ); - - const duplicateRiskLevel = locationTransferTraces.some( - // @ts-ignore - any until models are typed - locationTransferTrace => - locationTransferTrace.RiskLevels.some( - // @ts-ignore - any until models are typed - riskLevel => riskLevel.level === RISK_LEVEL_2 - ) - ); - - if (duplicateRiskLevel) return response.sendStatus(status.CONFLICT); - - // @ts-ignore - any until models are typed - const riskLevels = locationTransferTraces.map(locationTransferTrace => ({ - level: RISK_LEVEL_2, - locationTransferTraceId: locationTransferTrace.uuid, - })); - - await database.RiskLevel.bulkCreate(riskLevels); - - return response.sendStatus(status.NO_CONTENT); - } -); - router.get( '/:locationTransferId', requireHealthDepartmentEmployee, @@ -127,30 +75,16 @@ router.post( locationTransferId: locationTransfer.uuid, traceId: { [Op.in]: request.body.traceIds }, }, - include: { - model: database.RiskLevel, - }, } ); - const duplicateRiskLevel = locationTransferTraces.some( - // @ts-ignore - any until models are typed - locationTransferTrace => - locationTransferTrace.RiskLevels.some( - // @ts-ignore - any until models are typed - riskLevel => riskLevel.level === RISK_LEVEL_3 - ) - ); - - if (duplicateRiskLevel) return response.sendStatus(status.CONFLICT); - // @ts-ignore - any until models are typed const riskLevels = locationTransferTraces.map(locationTransferTrace => ({ - level: RISK_LEVEL_3, + level: request.body.riskLevel, locationTransferTraceId: locationTransferTrace.uuid, })); - database.RiskLevel.bulkCreate(riskLevels); + database.RiskLevel.bulkCreate(riskLevels, { ignoreDuplicates: true }); return response.sendStatus(status.NO_CONTENT); } diff --git a/services/backend/src/utils/notifications/notificationsV4.ts b/services/backend/src/utils/notifications/notificationsV4.ts index 4c360d2..361b60d 100644 --- a/services/backend/src/utils/notifications/notificationsV4.ts +++ b/services/backend/src/utils/notifications/notificationsV4.ts @@ -138,7 +138,7 @@ export const generateChunk = async () => { const dummyTraces = await database.DummyTrace.findAll({ attributes: ['healthDepartmentId', 'traceId'], - where: { createdAt: { [Op.gt]: startTime } }, + where: { updatedAt: { [Op.gt]: startTime } }, }); const completedHashHexStrings: string[] = completedLocationTransfers.flatMap( diff --git a/services/contact-form/package.json b/services/contact-form/package.json index bd429e1..4de2cb1 100644 --- a/services/contact-form/package.json +++ b/services/contact-form/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/contact-form", - "version": "2.0.0", + "version": "2.0.1", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/health-department/package.json b/services/health-department/package.json index d7e81bb..5fe9765 100644 --- a/services/health-department/package.json +++ b/services/health-department/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/health-department", - "version": "2.0.0", + "version": "2.0.1", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/NotificationTrigger/NotificationTrigger.react.js b/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/NotificationTrigger/NotificationTrigger.react.js index 4668ec1..7622dec 100644 --- a/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/NotificationTrigger/NotificationTrigger.react.js +++ b/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/NotificationTrigger/NotificationTrigger.react.js @@ -3,7 +3,8 @@ import { useIntl } from 'react-intl'; import { Tooltip } from 'antd'; import { useQuery } from 'react-query'; -import { RISK_LEVEL_2 } from 'constants/riskLevels'; +import { RISK_LEVEL_2, RISK_LEVEL_3 } from 'constants/riskLevels'; +import { NOTIFIEABLE_DEVICE_TYPES } from 'constants/deviceTypes'; import { getWarningLevelsForLocationTransfer, @@ -16,6 +17,27 @@ import { NotificationModal } from 'components/App/modals/NotificationModal'; import { BellIcon, ButtonWrapper } from './NotificationTrigger.styled'; +const checkIfAnyContactPersonsAreNotifyable = (contactPersons, riskLevels) => { + const allowedDeviceTypes = new Set([ + NOTIFIEABLE_DEVICE_TYPES.IOS, + NOTIFIEABLE_DEVICE_TYPES.ANDROID, + ]); + const riskLevelsToCheck = [RISK_LEVEL_2, RISK_LEVEL_3]; + + const tracesOfNotifyableDevices = contactPersons.traces.filter( + contactPerson => allowedDeviceTypes.has(contactPerson.deviceType) + ); + return tracesOfNotifyableDevices.some(({ traceId }) => { + const riskLevelsForTrace = riskLevels.find( + riskLevel => riskLevel.traceId === traceId + ); + if (!riskLevelsForTrace.riskLevels.length) return true; + return riskLevelsToCheck.some( + level => !riskLevelsForTrace.riskLevels.includes(level) + ); + }); +}; + export const NotificationTrigger = ({ location }) => { const intl = useIntl(); const [openModal] = useModal(); @@ -71,16 +93,17 @@ export const NotificationTrigger = ({ location }) => { ) return null; - const isNotificationDisabled = - contactPersons.traces.length === 0 || - riskLevels.some(traceRisk => traceRisk.riskLevels.includes(RISK_LEVEL_2)); + const isNotificationEnabled = checkIfAnyContactPersonsAreNotifyable( + contactPersons, + riskLevels + ); return ( <ButtonWrapper - onClick={isNotificationDisabled ? () => {} : openNotificationModal} + onClick={isNotificationEnabled ? openNotificationModal : () => {}} > <Tooltip title={intl.formatMessage({ id: 'modal.notification.button' })}> - <BellIcon disabled={isNotificationDisabled} /> + <BellIcon disabled={!isNotificationEnabled} /> </Tooltip> </ButtonWrapper> ); diff --git a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestList.react.js b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestList.react.js index deafd1e..0c5b1d8 100644 --- a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestList.react.js +++ b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestList.react.js @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useIntl } from 'react-intl'; import { message } from 'antd'; +import { useQuery } from 'react-query'; import { decryptTrace } from 'utils/cryptoOperations'; import { INITIAL_OVERLAP_VALUE } from 'constants/timeOverlap'; @@ -18,12 +19,12 @@ export const GuestList = ({ location, }) => { const intl = useIntl(); - const [traces, setTraces] = useState(null); const [filteredTraces, setFilteredTraces] = useState(null); const [minTimeOverlap, setMinTimeOverlap] = useState(INITIAL_OVERLAP_VALUE); - useEffect(() => { - async function decryptTraces() { + const { data: traces } = useQuery( + `decryptedTraces${location.transferId}`, + async () => { const hideMessage = message.loading( intl.formatMessage({ id: 'message.decryptingData' }), 0 @@ -38,18 +39,17 @@ export const GuestList = ({ }) ) ); - - const validDecrypedTraces = decryptedTraces.filter( + hideMessage(); + return decryptedTraces.filter( user => !(user.isInvalid || user.isUnregistered) ); + }, + { staleTime: Number.POSITIVE_INFINITY } + ); - setTraces(validDecrypedTraces); - setDecryptedTraces(validDecrypedTraces); - hideMessage(); - } - - decryptTraces(); - }, [encryptedTraces, intl, setDecryptedTraces]); + useEffect(() => { + if (traces) setDecryptedTraces(traces); + }, [traces, setDecryptedTraces]); useEffect(() => { if (!traces) return; diff --git a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestListTable/GuestListTable.react.js b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestListTable/GuestListTable.react.js index 6f3ef53..5fc4d1b 100644 --- a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestListTable/GuestListTable.react.js +++ b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/GuestList/GuestListTable/GuestListTable.react.js @@ -5,7 +5,7 @@ import { getWarningLevelsForLocationTransfer, getMe } from 'network/api'; import { useQuery } from 'react-query'; -import { DEVICE_TYPES } from '../../Notify/Notify.helper'; +import { NOTIFIEABLE_DEVICE_TYPES } from 'constants/deviceTypes'; import { AdditionalData } from './AdditionalData'; import { Notified } from './Notified'; import { Name } from './Name'; @@ -98,8 +98,8 @@ export const GuestListTable = ({ !healthDepartmentEmployee || !healthDepartmentEmployee.notificationsEnabled || !( - notificationTrace.deviceType === DEVICE_TYPES.IOS || - notificationTrace.deviceType === DEVICE_TYPES.ANDROID + notificationTrace.deviceType === NOTIFIEABLE_DEVICE_TYPES.IOS || + notificationTrace.deviceType === NOTIFIEABLE_DEVICE_TYPES.ANDROID ) ) return null; diff --git a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.helper.js b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.helper.js deleted file mode 100644 index 88ce93f..0000000 --- a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.helper.js +++ /dev/null @@ -1,11 +0,0 @@ -export const DEVICE_TYPES = { - IOS: 0, - ANDROID: 1, -}; - -export const filterByDeviceType = contactPersons => - contactPersons.filter( - contact => - contact.deviceType === DEVICE_TYPES.IOS || - contact.deviceType === DEVICE_TYPES.ANDROID - ); diff --git a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.react.js b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.react.js index 4763f6e..eac6eef 100644 --- a/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.react.js +++ b/services/health-department/src/components/App/modals/ContactPersonsModal/ContactPersons/Notify/Notify.react.js @@ -5,7 +5,6 @@ import { useModal } from 'components/hooks/useModal'; import { getMe } from 'network/api'; import { NotificationModal } from 'components/App/modals/NotificationModal'; import { StyledLink, Wrapper, BellOutlinedIcon } from './Notify.styled'; -import { filterByDeviceType } from './Notify.helper'; export const Notify = ({ traces, location }) => { const intl = useIntl(); @@ -23,9 +22,6 @@ export const Notify = ({ traces, location }) => { if (!traces || traces.length === 0) return null; - const selectedFilteredTraces = filterByDeviceType(traces); - const selectedTracesIds = selectedFilteredTraces.map(trace => trace.traceId); - const openNotificationModal = () => { openModal({ title: intl.formatMessage({ @@ -36,7 +32,7 @@ export const Notify = ({ traces, location }) => { locationId={locationId} locationName={locationName} locationTransferId={locationTransferId} - traceIds={selectedTracesIds} + traces={traces} time={time} departmentId={healthDepartmentEmployee.departmentId} /> diff --git a/services/health-department/src/components/App/modals/ModalArea/ModalArea.react.js b/services/health-department/src/components/App/modals/ModalArea/ModalArea.react.js index b1ef718..b61abb6 100644 --- a/services/health-department/src/components/App/modals/ModalArea/ModalArea.react.js +++ b/services/health-department/src/components/App/modals/ModalArea/ModalArea.react.js @@ -22,7 +22,7 @@ export const ModalArea = () => { // Use a copy of the modal content in order to complete the closing animation of the modal with content useEffect(() => { - if (stateModal) setModalContent(stateModal); + if (stateModal) setModalContent([...stateModal].pop()); }, [stateModal]); if (!modalContent) return null; diff --git a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.helper.js b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.helper.js index 54348b7..8a1ab91 100644 --- a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.helper.js +++ b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.helper.js @@ -1,11 +1,17 @@ -import { RISK_LEVEL_3 } from 'constants/riskLevels'; +import { NOTIFIEABLE_DEVICE_TYPES } from 'constants/deviceTypes'; -export const filterLevel3RiskLevels = (traceIdsToFilter, riskLevels) => +export const filterByDeviceType = contactPersons => + contactPersons.filter( + contact => + contact.deviceType === NOTIFIEABLE_DEVICE_TYPES.IOS || + contact.deviceType === NOTIFIEABLE_DEVICE_TYPES.ANDROID + ); + +export const filterRiskLevels = (traceIdsToFilter, riskLevels, level) => traceIdsToFilter.filter(traceId => riskLevels.some( riskLevel => - riskLevel.traceId === traceId && - !riskLevel.riskLevels.includes(RISK_LEVEL_3) + riskLevel.traceId === traceId && !riskLevel.riskLevels.includes(level) ) ); diff --git a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.react.js b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.react.js index d483e3e..cdbcf29 100644 --- a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.react.js +++ b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.react.js @@ -3,7 +3,6 @@ import { useIntl } from 'react-intl'; import { Popconfirm, notification } from 'antd'; import { useQuery, useQueryClient } from 'react-query'; import { - notifyLocationGuests, notifyLocationTracesGuests, getNotificationConfig, getContactPersons, @@ -13,7 +12,6 @@ import { formattedTimeLabel } from 'utils/time'; import { useModal } from 'components/hooks/useModal'; import { PrimaryButton } from 'components/general'; import { RISK_LEVEL_2, RISK_LEVEL_3 } from 'constants/riskLevels'; -import { filterByDeviceType } from '../ContactPersonsModal/ContactPersons/Notify/Notify.helper'; import { Wrapper, SectionTitle, @@ -22,25 +20,25 @@ import { SwitchDescription, StyledSwitch, SwitchWrapper, - Warning, } from './NotificationModal.styled'; import { - filterLevel3RiskLevels, + filterRiskLevels, getLocaleObject, + filterByDeviceType, } from './NotificationModal.helper'; // eslint-disable-next-line complexity export const NotificationModal = ({ locationName, locationTransferId, - traceIds, + traces, time, departmentId, }) => { const intl = useIntl(); const queryClient = useQueryClient(); const [, closeModal] = useModal(); - const [level, setLevel] = useState(traceIds ? RISK_LEVEL_3 : RISK_LEVEL_2); + const [level, setLevel] = useState(RISK_LEVEL_2); const setRiskLevel2 = checked => checked ? setLevel(RISK_LEVEL_2) : setLevel(RISK_LEVEL_3); @@ -82,17 +80,29 @@ export const NotificationModal = ({ if (!config || !contactPersons || !riskLevels) return null; - const contactPersonFiltered = filterByDeviceType(contactPersons.traces); + const triggeredWithoutSelection = !traces; + const filteredContactPersons = filterByDeviceType( + triggeredWithoutSelection ? contactPersons.traces : traces + ); + const localeObject = getLocaleObject(config, departmentId, level, intl); + const traceIdsToNotify = filterRiskLevels( + filteredContactPersons.map(trace => trace.traceId), + riskLevels, + level + ); + const completeTracesLength = triggeredWithoutSelection + ? contactPersons.traces.length + : traces.length; + const amountOfNotifyableTraces = traceIdsToNotify.length; + const amountOfNonNotifyableTraces = + completeTracesLength - amountOfNotifyableTraces; const notify = () => { - const notificationRequest = - level === RISK_LEVEL_2 - ? notifyLocationGuests(locationTransferId) - : notifyLocationTracesGuests({ - traceIds: - traceIds || contactPersonFiltered.map(trace => trace.traceId), - locationTransferId, - }); + const notificationRequest = notifyLocationTracesGuests({ + traceIds: traceIdsToNotify, + locationTransferId, + riskLevel: level, + }); notificationRequest .then(response => { @@ -113,17 +123,6 @@ export const NotificationModal = ({ .catch(() => triggerNotificationError()); }; - const localeObject = getLocaleObject(config, departmentId, level, intl); - const level3TraceIds = filterLevel3RiskLevels( - traceIds || contactPersonFiltered.map(trace => trace.traceId), - riskLevels - ); - const wasLevel2Triggered = riskLevels.some(traceRisk => - traceRisk.riskLevels.includes(RISK_LEVEL_2) - ); - const isButtonDisabled = - level3TraceIds.length === 0 && level === RISK_LEVEL_3; - return ( <Wrapper> <SectionTitle> @@ -136,38 +135,30 @@ export const NotificationModal = ({ {intl.formatMessage({ id: 'modal.notification.section1' })} </Section> - {!wasLevel2Triggered && ( - <Section> - <SwitchWrapper> - <StyledSwitch - checked={level === RISK_LEVEL_2} - onChange={setRiskLevel2} - /> - <SwitchDescription> - {intl.formatMessage({ - id: 'modal.notification.selection.potentialInfectionRisk', - })} - </SwitchDescription> - </SwitchWrapper> - <SwitchWrapper> - <StyledSwitch - checked={level === RISK_LEVEL_3} - onChange={setRiskLevel3} - /> - <SwitchDescription> - {intl.formatMessage({ - id: 'modal.notification.selection.elevatedInfectionRisk', - })} - </SwitchDescription> - </SwitchWrapper> - </Section> - )} - - {traceIds && level === RISK_LEVEL_2 && ( - <Warning> - {intl.formatMessage({ id: 'modal.notification.selectionWarning' })} - </Warning> - )} + <Section> + <SwitchWrapper> + <StyledSwitch + checked={level === RISK_LEVEL_2} + onChange={setRiskLevel2} + /> + <SwitchDescription> + {intl.formatMessage({ + id: 'modal.notification.selection.potentialInfectionRisk', + })} + </SwitchDescription> + </SwitchWrapper> + <SwitchWrapper> + <StyledSwitch + checked={level === RISK_LEVEL_3} + onChange={setRiskLevel3} + /> + <SwitchDescription> + {intl.formatMessage({ + id: 'modal.notification.selection.elevatedInfectionRisk', + })} + </SwitchDescription> + </SwitchWrapper> + </Section> <SectionTitle> {intl.formatMessage({ id: 'modal.notification.section2.title' })} @@ -199,7 +190,6 @@ export const NotificationModal = ({ } )} </Section> - <SectionTitle> {intl.formatMessage({ id: 'modal.notification.section3.title' })} </SectionTitle> @@ -218,23 +208,22 @@ export const NotificationModal = ({ {intl.formatMessage( { id: 'modal.notification.section4.title' }, { - guestCount: - level === RISK_LEVEL_2 - ? contactPersonFiltered.length - : level3TraceIds.length, - } - )} - </SectionTitle> - <SectionTitle> - {intl.formatMessage( - { id: 'modal.notification.selection.countNoNotification' }, - { - amount: contactPersons.traces.length - contactPersonFiltered.length, + guestCount: amountOfNotifyableTraces, } )} </SectionTitle> + {amountOfNonNotifyableTraces !== 0 && ( + <SectionTitle> + {intl.formatMessage( + { id: 'modal.notification.selection.countNoNotification' }, + { + amount: amountOfNonNotifyableTraces, + } + )} + </SectionTitle> + )} <ButtonWrapper> - {isButtonDisabled ? ( + {amountOfNotifyableTraces === 0 ? ( <PrimaryButton disabled> {intl.formatMessage({ id: 'modal.notification.button.alreadyNotified', @@ -249,10 +238,7 @@ export const NotificationModal = ({ id: 'modal.notification.confirmation', }, { - guestCount: - level === RISK_LEVEL_2 - ? contactPersonFiltered.length - : level3TraceIds.length, + guestCount: traceIdsToNotify.length, } )} okText={intl.formatMessage({ diff --git a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.styled.js b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.styled.js index cd1054b..03599fa 100644 --- a/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.styled.js +++ b/services/health-department/src/components/App/modals/NotificationModal/NotificationModal.styled.js @@ -6,14 +6,6 @@ export const Wrapper = styled.div` flex-direction: column; `; -export const Warning = styled.div` - color: rgb(241, 103, 4); - font-family: Montserrat-Medium, sans-serif; - font-size: 16px; - font-weight: 500; - margin-bottom: 24px; -`; - export const SectionTitle = styled.div` font-family: Montserrat-Bold, sans-serif; font-size: 16px; diff --git a/services/health-department/src/constants/deviceTypes.js b/services/health-department/src/constants/deviceTypes.js new file mode 100644 index 0000000..076bbec --- /dev/null +++ b/services/health-department/src/constants/deviceTypes.js @@ -0,0 +1,4 @@ +export const NOTIFIEABLE_DEVICE_TYPES = { + IOS: 0, + ANDROID: 1, +}; diff --git a/services/health-department/src/messages/de.json b/services/health-department/src/messages/de.json index b6d6a35..e721a71 100644 --- a/services/health-department/src/messages/de.json +++ b/services/health-department/src/messages/de.json @@ -382,15 +382,15 @@ "notification.notification.success": "Benachrichtigungen gesendet.", "notification.notification.error": "Benachrichtigungen konnten nicht gesendet werden.", "ContactPerson.notify.title": "AUSWAHL BENACHRICHTIGEN", - "profile.contactInformation.required": "Bitte gib wenigstens eine Telefonnummer und/oder eine E-Mail-Adresse an.", + "profile.contactInformation.required": "Bitte gib eine Telefonnummer und/oder eine E-Mail-Adresse an.", "modal.notification.section3": "Alle luca App-Nutzer:innen, die in diesem Zeitraum bei {locationName} eingecheckt waren, erhalten die Benachrichtigung.\n{br}Von: {from}\n{br}Bis: {till}", "modal.notification.section3.title": "Zeitraum", "processDetails.note.invalidSignature": "Die Signatur der Notiz ist falsch!", "modal.notification.notSpecified": "Keine Angabe", "modal.notification.selection.potentialInfectionRisk": "Mögliches Infektionsrisiko", "modal.notification.selection.elevatedInfectionRisk": "Erhöhtes Infektionsrisiko", - "modal.notification.button.alreadyNotified": "Bereits informiert", + "modal.notification.button.alreadyNotified": "Bereits benachrichtigt", "contactperson.notified": "Hinweis ausgelöst", - "modal.notification.selectionWarning": "Diese Warnmeldung kann nur an alle Personen in der Kontaktliste ausgespielt werden.", - "modal.notification.selection.countNoNotification": "{amount} Personen werden nicht benachrichtigt, da der Check-in via Schlüsselanhänger, Kontaktformular oder Web App erfolgte" + "modal.notification.selectionWarning": "Wichtige Information: Der Hinweis \"Mögliches Infektionsrisiko\" wird an alle Personen ausgespielt, die im jeweiligen Zeitrahmen in der Location eingecheckt waren. Ihre Auswahl aus dem vorherigen Schritt ist nicht mehr gültig.", + "modal.notification.selection.countNoNotification": "{amount} Personen werden nicht benachrichtigt, da der Check-in via Schlüsselanhänger, Kontaktformular oder Web App erfolgte oder weil diese Person bereits benachrichtigt wurde." } \ No newline at end of file diff --git a/services/health-department/src/messages/en.json b/services/health-department/src/messages/en.json index 990acae..1825577 100644 --- a/services/health-department/src/messages/en.json +++ b/services/health-department/src/messages/en.json @@ -59,7 +59,7 @@ "modal.trackInfection.confirmation": "Are you sure you want to track a new infection chain? This action cannot be undone.", "modal.trackInfection.confirmButton": "Start tracking", "modal.trackInfection.declineButton": "Cancel", - "modal.searchGroup.title": "Find groups", + "modal.searchGroup.title": "Find locations", "modal.addEmployee.title": "Add Employee", "navigation.profile": "Profile", "navigation.tracking": "Tracking", @@ -94,12 +94,12 @@ "history.contactInformation": "Contact person", "history.locations": "Locations", "history.confirmed": "CONFIRMED", - "manualSearch.button": "Search for Groups", - "groupSearch.form.group.placeholder": "Search for groups by name", + "manualSearch.button": "Search for locations", + "groupSearch.form.group.placeholder": "Search for locations by name", "groupSearch.form.button": "Search", "groupSearch.form.request": "Request data", "groupSearch.form.error.minLength": "Please enter at least 3 characters", - "groupSearch.table.empty": "No groups found", + "groupSearch.table.empty": "No locations found", "groupSearch.requestData.info": "Please select a timespan to request data from {group}.", "userManagement.email": "Email", "userManagement.firstName": "Firstname", @@ -382,9 +382,9 @@ "notification.notification.success": "Notifications sent.", "notification.notification.error": "Notifications could not be sent.", "ContactPerson.notify.title": "NOTIFY SELECTION", - "profile.contactInformation.required": "Please provide at least a phone number and/or an email address.", + "profile.contactInformation.required": "Please provide a phone number and/or an email address.", "modal.notification.section3": "All luca app users who were checked into {locationName} during this timeframe will receive this notification.\n{br}From: {from}\n{br}To: {till}", - "modal.notification.section3.title": "Timeframe", + "modal.notification.section3.title": "Time period", "processDetails.note.invalidSignature": "Note signature is wrong!", "modal.notification.notSpecified": "Not specified", "modal.notification.selection.potentialInfectionRisk": "Potential infection risk", @@ -392,5 +392,5 @@ "modal.notification.button.alreadyNotified": "Already notified", "contactperson.notified": "Notification sent", "modal.notification.selectionWarning": "This warning can only be triggered for all contact persons.", - "modal.notification.selection.countNoNotification": "{amount} people won't receive this notification as the check-in occured via badge, contact form or web app." + "modal.notification.selection.countNoNotification": "{amount} persons won't receive this notification as the check-in occured via badge, contact form or web app or because they have already been notified." } \ No newline at end of file diff --git a/services/health-department/src/network/api.js b/services/health-department/src/network/api.js index c8085f4..46b57b7 100644 --- a/services/health-department/src/network/api.js +++ b/services/health-department/src/network/api.js @@ -294,15 +294,6 @@ export const createUserTransfer = payload => { }; // NOTIFICATIONS -export const notifyLocationGuests = locationTransferId => - fetch(`${API_PATH}/v4/riskLevels`, { - method: 'POST', - body: JSON.stringify({ - locationTransferId, - }), - headers, - }); - export const notifyLocationTracesGuests = payload => fetch(`${API_PATH}/v4/riskLevels/traces`, { method: 'POST', diff --git a/services/health-department/src/reducers/modals.js b/services/health-department/src/reducers/modals.js index e2dc3a5..1dec546 100644 --- a/services/health-department/src/reducers/modals.js +++ b/services/health-department/src/reducers/modals.js @@ -1,14 +1,16 @@ // Actions import { OPEN_MODAL, CLOSE_MODAL } from 'actions/modals'; -const initialState = null; +const initialState = []; export const modalsReducer = (state = initialState, action) => { switch (action.type) { case OPEN_MODAL: - return action.payload; + state.push(action.payload); + return [...state]; case CLOSE_MODAL: - return null; + state.pop(); + return [...state]; default: return state; } diff --git a/services/locations/package.json b/services/locations/package.json index 6abd860..f18697d 100644 --- a/services/locations/package.json +++ b/services/locations/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/locations", - "version": "2.0.0", + "version": "2.0.1", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/scanner/package.json b/services/scanner/package.json index 890951f..9e119e1 100644 --- a/services/scanner/package.json +++ b/services/scanner/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/scanner", - "version": "2.0.0", + "version": "2.0.1", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/webapp/package.json b/services/webapp/package.json index 5d75248..cb8e455 100644 --- a/services/webapp/package.json +++ b/services/webapp/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/webapp", - "version": "2.0.0", + "version": "2.0.1", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", -- GitLab