From 8ce387bd0d96825871dcc341117f51d3c4725742 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Wed, 9 Oct 2024 07:14:26 -0500 Subject: [PATCH 01/15] Redirect to duplicate applet setting on menu item click --- .../Applets/AppletsTable/AppletItem/AppletItem.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/modules/Dashboard/features/Applets/AppletsTable/AppletItem/AppletItem.tsx b/src/modules/Dashboard/features/Applets/AppletsTable/AppletItem/AppletItem.tsx index c2850349e..d90d76338 100644 --- a/src/modules/Dashboard/features/Applets/AppletsTable/AppletItem/AppletItem.tsx +++ b/src/modules/Dashboard/features/Applets/AppletsTable/AppletItem/AppletItem.tsx @@ -21,6 +21,7 @@ import { getDateInUserTimezone, getEncryptionToServer, Mixpanel, + SettingParam, } from 'shared/utils'; import { useAppletsDnd } from 'modules/Dashboard/features/Applets/AppletsTable/AppletsTable.hooks'; import { ShareAppletPopup } from 'modules/Dashboard/features/Applets/Popups'; @@ -146,14 +147,11 @@ export const AppletItem = ({ item, onPublish, enableShareToLibrary }: AppletItem ), ), duplicateAction: () => - checkAppletEncryption(() => - dispatch( - popups.actions.setPopupVisible({ - applet: item, - key: 'duplicatePopupsVisible', - value: true, - }), - ), + navigate( + generatePath(page.appletSettingsItem, { + appletId: item.id, + setting: SettingParam.DuplicateApplet, + }), ), transferOwnership: () => checkAppletEncryption(() => From aca7c8f00a29c4bd6badf05ee7a069b9b85a5473 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Wed, 9 Oct 2024 08:26:27 -0500 Subject: [PATCH 02/15] Remove duplicate popup from Applets tab --- src/modules/Dashboard/features/Applets/Applets.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/modules/Dashboard/features/Applets/Applets.tsx b/src/modules/Dashboard/features/Applets/Applets.tsx index 18a936762..c2a74efa6 100644 --- a/src/modules/Dashboard/features/Applets/Applets.tsx +++ b/src/modules/Dashboard/features/Applets/Applets.tsx @@ -7,7 +7,6 @@ import { v4 as uuidv4 } from 'uuid'; import { Folder, Applet } from 'api'; import { DeletePopup, - DuplicatePopups, PublishConcealAppletPopup, TransferOwnershipPopup, } from 'modules/Dashboard/features/Applet/Popups'; @@ -39,12 +38,8 @@ export const Applets = () => { const { ownerId } = workspaces.useData() || {}; - const { - duplicatePopupsVisible, - deletePopupVisible, - transferOwnershipPopupVisible, - publishConcealPopupVisible, - } = popups.useData(); + const { deletePopupVisible, transferOwnershipPopupVisible, publishConcealPopupVisible } = + popups.useData(); const { fetchData, isLoading, count, expandedFolders, expandFolder, collapseFolder } = useAppletsWithFolders(setRows); @@ -173,7 +168,6 @@ export const Applets = () => { data-testid="dashboard-applets-table" {...tableProps} /> - {duplicatePopupsVisible && } {deletePopupVisible && ( )} From 76d58e8411db50d2a7095067ec49cf5fcbbdbd22 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Wed, 9 Oct 2024 10:03:57 -0500 Subject: [PATCH 03/15] Revert "Remove duplicate popup from Applets tab" This reverts commit aca7c8f00a29c4bd6badf05ee7a069b9b85a5473. --- src/modules/Dashboard/features/Applets/Applets.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/Dashboard/features/Applets/Applets.tsx b/src/modules/Dashboard/features/Applets/Applets.tsx index c2a74efa6..18a936762 100644 --- a/src/modules/Dashboard/features/Applets/Applets.tsx +++ b/src/modules/Dashboard/features/Applets/Applets.tsx @@ -7,6 +7,7 @@ import { v4 as uuidv4 } from 'uuid'; import { Folder, Applet } from 'api'; import { DeletePopup, + DuplicatePopups, PublishConcealAppletPopup, TransferOwnershipPopup, } from 'modules/Dashboard/features/Applet/Popups'; @@ -38,8 +39,12 @@ export const Applets = () => { const { ownerId } = workspaces.useData() || {}; - const { deletePopupVisible, transferOwnershipPopupVisible, publishConcealPopupVisible } = - popups.useData(); + const { + duplicatePopupsVisible, + deletePopupVisible, + transferOwnershipPopupVisible, + publishConcealPopupVisible, + } = popups.useData(); const { fetchData, isLoading, count, expandedFolders, expandFolder, collapseFolder } = useAppletsWithFolders(setRows); @@ -168,6 +173,7 @@ export const Applets = () => { data-testid="dashboard-applets-table" {...tableProps} /> + {duplicatePopupsVisible && } {deletePopupVisible && ( )} From f7b253bba9ca0ff8c244d9b69566355f2d0c9b1b Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Wed, 9 Oct 2024 10:04:00 -0500 Subject: [PATCH 04/15] Revert "Redirect to duplicate applet setting on menu item click" This reverts commit 8ce387bd0d96825871dcc341117f51d3c4725742. --- .../Applets/AppletsTable/AppletItem/AppletItem.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/modules/Dashboard/features/Applets/AppletsTable/AppletItem/AppletItem.tsx b/src/modules/Dashboard/features/Applets/AppletsTable/AppletItem/AppletItem.tsx index d90d76338..c2850349e 100644 --- a/src/modules/Dashboard/features/Applets/AppletsTable/AppletItem/AppletItem.tsx +++ b/src/modules/Dashboard/features/Applets/AppletsTable/AppletItem/AppletItem.tsx @@ -21,7 +21,6 @@ import { getDateInUserTimezone, getEncryptionToServer, Mixpanel, - SettingParam, } from 'shared/utils'; import { useAppletsDnd } from 'modules/Dashboard/features/Applets/AppletsTable/AppletsTable.hooks'; import { ShareAppletPopup } from 'modules/Dashboard/features/Applets/Popups'; @@ -147,11 +146,14 @@ export const AppletItem = ({ item, onPublish, enableShareToLibrary }: AppletItem ), ), duplicateAction: () => - navigate( - generatePath(page.appletSettingsItem, { - appletId: item.id, - setting: SettingParam.DuplicateApplet, - }), + checkAppletEncryption(() => + dispatch( + popups.actions.setPopupVisible({ + applet: item, + key: 'duplicatePopupsVisible', + value: true, + }), + ), ), transferOwnership: () => checkAppletEncryption(() => From 69995f52ceac00dd8f05ee2730c1ee3d12a286a7 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Thu, 10 Oct 2024 09:45:19 -0500 Subject: [PATCH 05/15] Allow customization of the Checkbox sx prop inside the CheckboxController --- .../FormComponents/CheckboxController/CheckboxController.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/components/FormComponents/CheckboxController/CheckboxController.tsx b/src/shared/components/FormComponents/CheckboxController/CheckboxController.tsx index 023195211..7cf9c41b3 100644 --- a/src/shared/components/FormComponents/CheckboxController/CheckboxController.tsx +++ b/src/shared/components/FormComponents/CheckboxController/CheckboxController.tsx @@ -39,7 +39,7 @@ export const CheckboxController = ({ control={ { From 098f0b0424c5d2781c0d42a98644cdd9d0d30696 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Thu, 10 Oct 2024 09:46:09 -0500 Subject: [PATCH 06/15] Type the returned object from the duplicate endpoint --- src/modules/Dashboard/api/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Dashboard/api/api.ts b/src/modules/Dashboard/api/api.ts index d65e90596..47b5e689d 100644 --- a/src/modules/Dashboard/api/api.ts +++ b/src/modules/Dashboard/api/api.ts @@ -315,7 +315,7 @@ export const postSubjectInvitationApi = ( ); export const duplicateAppletApi = ({ appletId, options }: DuplicateApplet, signal?: AbortSignal) => - authApiClient.post( + authApiClient.post>( `/applets/${appletId}/duplicate`, { ...options }, { From 9f1fbe75fdf42dd6406542b7408065d8d6623aba Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Thu, 10 Oct 2024 09:46:31 -0500 Subject: [PATCH 07/15] Add support for the `includeReportServer` property on the duplicate endpoint --- src/modules/Dashboard/api/api.types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/Dashboard/api/api.types.ts b/src/modules/Dashboard/api/api.types.ts index 361c28ef1..98a135df8 100644 --- a/src/modules/Dashboard/api/api.types.ts +++ b/src/modules/Dashboard/api/api.types.ts @@ -274,6 +274,7 @@ export type DuplicateApplet = AppletId & { options: { encryption: Encryption; displayName: string; + includeReportServer?: boolean; }; }; From a12141f4d677f1b888dd939fc5da60d1ce569567 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Thu, 10 Oct 2024 09:48:26 -0500 Subject: [PATCH 08/15] Move appletId and ownerId to component props The `useCheckReportServer` hook was previously accessing these from the URL, which restricted its use to pages that contain them. It's cleaner for the caller to get these props from the URL (if present) and pass them instead, so that the hook may enjoy wider use --- .../ReportConfigSetting/ReportConfigSetting.hooks.ts | 9 ++++++--- .../features/ReportConfigSetting/ReportConfigSetting.tsx | 5 +++++ .../ReportConfigSetting/ReportConfigSetting.types.ts | 2 ++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks.ts b/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks.ts index a991c8b0a..c6d94b490 100644 --- a/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks.ts +++ b/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks.ts @@ -12,9 +12,12 @@ import { defaultValues as initialValues, } from './ReportConfigSetting.const'; -export const useCheckReportServer = ({ url, publicKey }: UseCheckReportServer) => { - const { appletId = '', ownerId = '' } = useParams(); - +export const useCheckReportServer = ({ + url, + publicKey, + appletId, + ownerId, +}: UseCheckReportServer) => { const onVerify = async () => { const token = authStorage.getAccessToken(); diff --git a/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.tsx b/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.tsx index 01d2465ce..ccf1310cf 100644 --- a/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.tsx +++ b/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.tsx @@ -4,6 +4,7 @@ import { useForm, useFormContext } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/material'; import { ObjectSchema } from 'yup'; +import { useParams } from 'react-router-dom'; import { postReportConfigApi, @@ -180,9 +181,13 @@ export const ReportConfigSetting = ({ 'data-testid': dataTestid }: ReportConfigS ) : null; + const { appletId = '', ownerId = '' } = useParams(); + const { onVerify, onSetPassword } = useCheckReportServer({ url: reportServerUrl, publicKey: reportPublicKey, + appletId, + ownerId, }); const handleAddEmail = async (value: string) => { diff --git a/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.types.ts b/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.types.ts index ba697bbbd..65f2af22b 100644 --- a/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.types.ts +++ b/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.types.ts @@ -22,6 +22,8 @@ export type VerifyReportServer = { export type UseCheckReportServer = { url: string; publicKey: string; + appletId: string; + ownerId: string; }; export type SetPasswordReportServer = { From 9df7afed9755801463227cbc89382ba858ca6946 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Thu, 10 Oct 2024 09:55:29 -0500 Subject: [PATCH 09/15] Add checkbox to duplication popups and implement logic --- .../DuplicatePopups/DuplicatePopups.tsx | 225 +++++++++++++----- .../DuplicatePopups/DuplicatePopups.types.ts | 1 + src/resources/app-en.json | 2 + src/resources/app-fr.json | 2 + 4 files changed, 175 insertions(+), 55 deletions(-) diff --git a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx index 9b0dd8a08..86f221906 100644 --- a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx +++ b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx @@ -4,16 +4,30 @@ import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; +import { Box } from '@mui/material'; -import { Encryption, getEncryptionToServer, Mixpanel } from 'shared/utils'; +import { + Encryption, + getEncryptionToServer, + getPrivateKey, + Mixpanel, + publicEncrypt, +} from 'shared/utils'; import { Modal, Spinner, SpinnerUiType } from 'shared/components'; -import { InputController } from 'shared/components/FormComponents'; -import { StyledErrorText, StyledModalWrapper, variables } from 'shared/styles'; +import { CheckboxController, InputController } from 'shared/components/FormComponents'; +import { StyledErrorText, StyledFlexColumn, StyledModalWrapper, variables } from 'shared/styles'; import { useAsync } from 'shared/hooks/useAsync'; import { useAppletPrivateKeySetter } from 'modules/Builder/hooks'; -import { applet, auth, banners, popups } from 'redux/modules'; +import { applet, auth, banners, popups, SingleApplet, workspaces } from 'redux/modules'; import { useAppDispatch } from 'redux/store'; -import { ApiResponseCodes, duplicateAppletApi, getAppletUniqueNameApi } from 'api'; +import { + ApiResponseCodes, + Applet, + duplicateAppletApi, + getAppletUniqueNameApi, + ResponseWithObject, +} from 'api'; +import { useCheckReportServer } from 'modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks'; import { AppletPasswordPopup, @@ -30,9 +44,15 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v const setAppletPrivateKey = useAppletPrivateKeySetter(); const { duplicatePopupsVisible, applet: appletData } = popups.useData(); const { result } = applet.useAppletData() || {}; - const currentApplet = appletData || result; + const currentApplet = { + ...(appletData || result), + reportServerIp: result?.reportServerIp, + reportPublicKey: result?.reportPublicKey, + } as SingleApplet | undefined; + const { getApplet } = applet.thunk; const currentAppletName = currentApplet?.displayName ?? ''; const currentAppletId = currentApplet?.id ?? ''; + const ownerId = workspaces.useData()?.ownerId ?? ''; const encryptionDataRef = useRef<{ encryption?: Encryption; password?: string; @@ -42,17 +62,38 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v const [errorModalVisible, setErrorModalVisible] = useState(false); const [nameModalVisible, setNameModalVisible] = useState(false); const [nameError, setNameError] = useState(null); + const [reportServerError, setReportServerError] = useState(null); + const [newAppletSuccessResponse, setNewAppletSuccessResponse] = useState + > | null>(null); + + const { onVerify: verifyCurrentAppletReportServer } = useCheckReportServer({ + url: currentApplet?.reportServerIp ?? '', + publicKey: currentApplet?.reportPublicKey ?? '', + appletId: currentAppletId, + ownerId, + }); - const { handleSubmit, control, trigger, getValues, setValue } = + const { onSetPassword: setNewAppletReportServerPassword } = useCheckReportServer({ + url: newAppletSuccessResponse?.data.result.reportServerIp ?? '', + publicKey: newAppletSuccessResponse?.data.result.reportPublicKey ?? '', + appletId: newAppletSuccessResponse?.data.result.id ?? '', + ownerId, + }); + + const { handleSubmit, control, trigger, watch, getValues, setValue } = useForm({ resolver: yupResolver( yup.object({ name: yup.string().required(t('nameRequired')!), + includeReportServer: yup.boolean().required(), }), ), - defaultValues: { name: '' }, + defaultValues: { name: '', includeReportServer: false }, }); + const { includeReportServer } = watch(); + const { execute: executeGetName, isLoading: isGetNameLoading } = useAsync( getAppletUniqueNameApi, (res) => { @@ -63,34 +104,10 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v }, ); - const { execute: executeGetNameSecond, isLoading: isGetNameSecondLoading } = useAsync( - getAppletUniqueNameApi, - (res) => { - const currentName = getValues('name'); - const nameFromApi = res?.data?.result?.name; - if (nameFromApi === currentName) { - setNameModalVisible(false); - setPasswordModalVisible(true); - - return; - } - setNameError(t('appletNameExists')); - }, - () => { - setErrorModalVisible(true); - }, - ); - const { execute: executeDuplicate, isLoading: isDuplicateLoading } = useAsync( duplicateAppletApi, async (res) => { - await setAppletPrivateKey({ - appletPassword: encryptionDataRef.current.password ?? '', - encryption: encryptionDataRef.current.encryption!, - appletId: currentAppletId, - }); - - handleDuplicateSuccess(res); + setNewAppletSuccessResponse(res); }, (error) => { setPasswordModalVisible(false); @@ -149,7 +166,7 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v ); }; - const submitCallback = async (ref?: AppletPasswordRefType) => { + const submitPasswordCallback = async (ref?: AppletPasswordRefType) => { const password = ref?.current?.password ?? ''; const encryption = await getEncryptionToServer(password, accountId ?? ''); encryptionDataRef.current = { @@ -161,6 +178,7 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v options: { encryption, displayName: getValues('name'), + includeReportServer, }, }); }; @@ -170,16 +188,44 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v setNameModalVisible(true); }; - const setNameHandler = async () => { - await executeGetNameSecond({ name: getValues('name') }); + const onSubmitNameModal = async () => { + let nameIsValid = false; + let reportServerIsValid = !includeReportServer; + + try { + const res = await getAppletUniqueNameApi({ name: getValues('name') }); + const currentName = getValues('name'); + const nameFromApi = res?.data?.result?.name; + if (nameFromApi === currentName) { + nameIsValid = true; + } else { + setNameError(t('appletNameExists')); + } + } catch (e) { + setErrorModalVisible(true); + } + + if (includeReportServer) { + reportServerIsValid = await verifyCurrentAppletReportServer(); + if (!reportServerIsValid) { + setReportServerError(t('reportServerInvalid')); + } else { + setReportServerError(null); + } + } + + if (nameIsValid && reportServerIsValid) { + setNameModalVisible(false); + setPasswordModalVisible(true); + } }; const handleNameChange = (event: ChangeEvent) => { setValue('name', event.target.value); setNameError(null); - trigger('name'); + void trigger('name'); }; - const isLoading = isGetNameLoading || isGetNameSecondLoading; + const isLoading = isGetNameLoading; const dataTestid = 'dashboard-applets-duplicate-popup'; useEffect(() => { @@ -190,13 +236,63 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v })(); }, [duplicatePopupsVisible]); + useEffect(() => { + async function effect() { + if (!newAppletSuccessResponse) return; + + await setAppletPrivateKey({ + appletPassword: encryptionDataRef.current.password ?? '', + encryption: encryptionDataRef.current.encryption!, + appletId: currentAppletId, + }); + + if (includeReportServer) { + // This request has a (very small) potential to fail. Ideally we would do this before the + // duplication takes place, but we can't since we need the ID of the new applet + // for the request. The best we could do is notify the user of the reason for the failure, + // but we don't have any proper workaround for this. + await setNewAppletReportServerPassword( + await publicEncrypt( + JSON.stringify({ + password: encryptionDataRef.current.password ?? '', + privateKey: await getPrivateKey({ + appletPassword: encryptionDataRef.current.password ?? '', + accountId: ownerId, + }), + }), + currentApplet?.reportPublicKey ?? '', + ), + ); + } + + handleDuplicateSuccess(newAppletSuccessResponse); + } + + void effect(); + }, [ + currentAppletId, + handleDuplicateSuccess, + newAppletSuccessResponse, + setAppletPrivateKey, + setNewAppletReportServerPassword, + ]); + + useEffect(() => { + async function effect() { + // Fetch the report server properties of the applet + await dispatch(getApplet({ appletId: currentAppletId })); + } + + void effect(); + }, [currentAppletId, dispatch, getApplet]); + return ( <> v <> {isLoading && } -
- + + + + + {nameError && ( + + {nameError} + + )} + + + + {nameError && ( + + {reportServerError} + + )} + + - {nameError && ( - - {nameError} - - )}
@@ -229,7 +344,7 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v onClose={passwordModalClose} popupType={AppletPasswordPopupType.Create} popupVisible={passwordModalVisible} - submitCallback={submitCallback} + submitCallback={submitPasswordCallback} isLoading={isDuplicateLoading} data-testid={`${dataTestid}-password-popup`} /> diff --git a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.types.ts b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.types.ts index feaab2ddc..42edd7ec2 100644 --- a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.types.ts +++ b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.types.ts @@ -1,4 +1,5 @@ export type DuplicatePopupsFormValues = { name: string; nameFromApi?: string; + includeReportServer: boolean; }; diff --git a/src/resources/app-en.json b/src/resources/app-en.json index fa6b0c35a..4f1cfd09e 100644 --- a/src/resources/app-en.json +++ b/src/resources/app-en.json @@ -515,6 +515,7 @@ "dropImg": "Drop Image here
or <3>click to browse.", "duplicate": "Duplicate", "duplicateApplet": "Duplicate Applet", + "duplicateAppletReportServer": "Include report server configuration", "duplicateDescription": "The current Applet's content will be copied and saved as a separate Applet.", "duration": "Duration (HH:MM)", "edit": "Edit", @@ -1178,6 +1179,7 @@ "repeatPasswordRequired": "Repeat Password is required", "replace": "Replace", "reportConfigNotSaved": "Report Configuration not saved. Save it?", + "reportServerInvalid": "The current report server is invalid. Please check the configuration.", "reportConfiguration": "Report Configuration", "reportEmailBody": "Please see the report attached to this email.", "reports": "Reports", diff --git a/src/resources/app-fr.json b/src/resources/app-fr.json index a968f6263..a6370c1aa 100644 --- a/src/resources/app-fr.json +++ b/src/resources/app-fr.json @@ -515,6 +515,7 @@ "dropImg": "Déposez l'image ici
ou <3>cliquez pour parcourir.", "duplicate": "Dupliquer", "duplicateApplet": "Dupliquer l'Applet", + "duplicateAppletReportServer": "Inclure la configuration du serveur de rapports", "duplicateDescription": "Le contenu actuel de l'Applet sera copié et enregistré en tant qu'Applet distinct.", "duration": "Durée (HH:MM)", "edit": "Modifier", @@ -1177,6 +1178,7 @@ "repeatPasswordRequired": "La répétition du mot de passe est requise", "replace": "Remplacer", "reportConfigNotSaved": "La configuration du rapport n'a pas été enregistrée. Voulez-vous l'enregistrer?", + "reportServerInvalid": "Le serveur de rapports actuel n'est pas valide. Veuillez vérifier la configuration.", "reportConfiguration": "Configuration du rapport", "reportEmailBody": "Veuillez consulter le rapport joint à cet e-mail.", "reports": "Rapports", From 5aeb1e07c203f887d6eb330f944f435217ae7cae Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Thu, 10 Oct 2024 10:31:41 -0500 Subject: [PATCH 10/15] Fix duplicate tests --- .../Popups/DuplicatePopups/DuplicatePopups.test.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx index 409128978..ec8906bc6 100644 --- a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx +++ b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx @@ -3,8 +3,9 @@ import mockAxios from 'jest-mock-axios'; import { expectBanner } from 'shared/utils'; import { renderWithProviders } from 'shared/utils/renderWithProviders'; -import { mockedApplet, mockedAppletData, mockedPassword } from 'shared/mock'; +import { mockedApplet, mockedAppletData, mockedAppletId, mockedPassword } from 'shared/mock'; import * as encryptionFunctions from 'shared/utils/encryption'; +import { mockGetRequestResponses } from 'shared/utils/axios-mocks'; import { DuplicatePopups } from './DuplicatePopups'; @@ -37,6 +38,7 @@ describe('DuplicatePopups', () => { }); test('should show an error if the name already exists in database', async () => { + mockAxios.post.mockResolvedValueOnce({ data: { result: mockedAppletData } }); mockAxios.post.mockResolvedValueOnce({ data: { result: { name: 'name' } } }); mockAxios.post.mockResolvedValueOnce({ data: { result: { name: 'name (1)' } } }); @@ -53,6 +55,10 @@ describe('DuplicatePopups', () => { }); test('should duplicate and open success modal', async () => { + mockGetRequestResponses({ + [`/applets/${mockedAppletId}`]: { data: { result: mockedAppletData } }, + }); + mockAxios.post.mockResolvedValueOnce({ data: { result: { name: 'name' } } }); mockAxios.post.mockResolvedValueOnce({ data: { result: { name: 'name' } } }); mockAxios.post.mockResolvedValueOnce({ data: { result: mockedAppletData } }); From cd79a6790789035e0cf0affd26c9a7f72d37d244 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Fri, 11 Oct 2024 10:36:44 -0500 Subject: [PATCH 11/15] Restrict useEffect to running only when the popup is visible --- .../Applet/Popups/DuplicatePopups/DuplicatePopups.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx index 86f221906..6dd456e1d 100644 --- a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx +++ b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx @@ -238,7 +238,7 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v useEffect(() => { async function effect() { - if (!newAppletSuccessResponse) return; + if (!newAppletSuccessResponse || !duplicatePopupsVisible) return; await setAppletPrivateKey({ appletPassword: encryptionDataRef.current.password ?? '', @@ -270,9 +270,13 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v void effect(); }, [ + currentApplet?.reportPublicKey, currentAppletId, + duplicatePopupsVisible, handleDuplicateSuccess, + includeReportServer, newAppletSuccessResponse, + ownerId, setAppletPrivateKey, setNewAppletReportServerPassword, ]); From 090d9e262bcac2e2a470339ca514cd23b2b68694 Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Fri, 11 Oct 2024 11:03:14 -0500 Subject: [PATCH 12/15] Fix other broken tests --- .../ReportConfigSetting.hooks.test.ts | 26 ++++++++++++++----- .../DuplicateAppletSettings.test.tsx | 6 ++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks.test.ts b/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks.test.ts index 1456144af..a9c90d93a 100644 --- a/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks.test.ts +++ b/src/modules/Builder/features/ReportConfigSetting/ReportConfigSetting.hooks.test.ts @@ -38,9 +38,15 @@ const renderReportServerHook = (message: string) => { (verifyReportServer as jest.Mock).mockResolvedValue({ json: async () => ({ message }), }); - (useParams as jest.Mock).mockReturnValue({ appletId: 'testAppletId', ownerId: 'testOwnerId' }); - return renderHook(() => useCheckReportServer({ url: mockUrl, publicKey: mockPublicKey })); + return renderHook(() => + useCheckReportServer({ + url: mockUrl, + publicKey: mockPublicKey, + appletId: 'testAppletId', + ownerId: 'testOwnerId', + }), + ); }; const renderDefaultValuesHook = (appletData = {}, params) => { @@ -104,10 +110,14 @@ describe('useCheckReportServer', () => { (setPasswordReportServer as jest.Mock).mockResolvedValue({ json: async () => ({ message: SUCCESS_MESSAGE }), }); - (useParams as jest.Mock).mockReturnValue({ appletId: 'testAppletId', ownerId: 'testOwnerId' }); const { result } = renderHook(() => - useCheckReportServer({ url: mockUrl, publicKey: 'mockPublicKey' }), + useCheckReportServer({ + url: mockUrl, + publicKey: 'mockPublicKey', + appletId: 'testAppletId', + ownerId: 'testOwnerId', + }), ); let setPasswordResult; @@ -131,10 +141,14 @@ describe('useCheckReportServer', () => { (setPasswordReportServer as jest.Mock).mockResolvedValue({ json: async () => ({ message: 'OTHER_MESSAGE' }), }); - (useParams as jest.Mock).mockReturnValue({ appletId: 'testAppletId', ownerId: 'testOwnerId' }); const { result } = renderHook(() => - useCheckReportServer({ url: mockUrl, publicKey: 'mockPublicKey' }), + useCheckReportServer({ + url: mockUrl, + publicKey: 'mockPublicKey', + appletId: 'testAppletId', + ownerId: 'testOwnerId', + }), ); let setPasswordResult; diff --git a/src/shared/features/AppletSettings/DuplicateAppletSettings/DuplicateAppletSettings.test.tsx b/src/shared/features/AppletSettings/DuplicateAppletSettings/DuplicateAppletSettings.test.tsx index 94a89e9fb..88c92fe9a 100644 --- a/src/shared/features/AppletSettings/DuplicateAppletSettings/DuplicateAppletSettings.test.tsx +++ b/src/shared/features/AppletSettings/DuplicateAppletSettings/DuplicateAppletSettings.test.tsx @@ -14,6 +14,7 @@ import { import { SettingParam, expectBanner } from 'shared/utils'; import { renderWithProviders } from 'shared/utils/renderWithProviders'; import * as encryptionFunctions from 'shared/utils/encryption'; +import { mockGetRequestResponses } from 'shared/utils/axios-mocks'; import { DuplicateAppletSettings } from './DuplicateAppletSettings'; @@ -70,9 +71,12 @@ jest.mock('react-router-dom', () => ({ describe('DuplicateAppletSettings', () => { test('should render and navigate to builder', async () => { + mockGetRequestResponses({ + [`/applets/${mockedAppletId}`]: { data: { result: mockedAppletData } }, + }); mockAxios.post.mockResolvedValueOnce({ data: { result: { name: 'name' } } }); mockAxios.post.mockResolvedValueOnce({ data: { result: { name: 'name' } } }); - mockAxios.post.mockResolvedValueOnce({ data: mockedAppletData }); + mockAxios.post.mockResolvedValueOnce({ data: { result: mockedAppletData } }); jest .spyOn(encryptionFunctions, 'getEncryptionToServer') .mockReturnValue(Promise.resolve(mockedEncryption)); From 0d1e09412b689a4af39131d1a91f544bdd3cb81b Mon Sep 17 00:00:00 2001 From: Marty <2614025+mbanting@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:32:19 -0700 Subject: [PATCH 13/15] Added TODO to add test(s) for duplicating with report server configuration https://mindlogger.atlassian.net/browse/M2-8037 --- .../Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx | 2 ++ .../DuplicateAppletSettings/DuplicateAppletSettings.test.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx index ec8906bc6..a624b2548 100644 --- a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx +++ b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.test.tsx @@ -108,6 +108,8 @@ describe('DuplicatePopups', () => { }); }); + // TODO Add test(s) for duplicating with report server configuration - https://mindlogger.atlassian.net/browse/M2-8037 + // TODO uncomment after useasync changes // test('should open error modal', async () => { // mockAxios.post.mockRejectedValue(new Error('error')); diff --git a/src/shared/features/AppletSettings/DuplicateAppletSettings/DuplicateAppletSettings.test.tsx b/src/shared/features/AppletSettings/DuplicateAppletSettings/DuplicateAppletSettings.test.tsx index 88c92fe9a..221bad8ef 100644 --- a/src/shared/features/AppletSettings/DuplicateAppletSettings/DuplicateAppletSettings.test.tsx +++ b/src/shared/features/AppletSettings/DuplicateAppletSettings/DuplicateAppletSettings.test.tsx @@ -115,4 +115,6 @@ describe('DuplicateAppletSettings', () => { expect(mockedUseNavigate).toBeCalledWith('/dashboard/applets'); }); + + // TODO Add test(s) for duplicating with report server configuration - https://mindlogger.atlassian.net/browse/M2-8037 }); From ded74603b2ccb2b4071017a0c99c3e99fb303d7c Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Mon, 14 Oct 2024 11:00:19 -0500 Subject: [PATCH 14/15] Hide checkbox if current applet has no report server configured --- .../DuplicatePopups/DuplicatePopups.tsx | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx index 6dd456e1d..54485aeac 100644 --- a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx +++ b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx @@ -58,6 +58,8 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v password?: string; }>({}); + const currentAppHasReportServerConfigured = !!currentApplet?.reportServerIp; + const [passwordModalVisible, setPasswordModalVisible] = useState(false); const [errorModalVisible, setErrorModalVisible] = useState(false); const [nameModalVisible, setNameModalVisible] = useState(false); @@ -323,20 +325,22 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v )} - - - {nameError && ( - - {reportServerError} - - )} - + {currentAppHasReportServerConfigured && ( + + + {nameError && ( + + {reportServerError} + + )} + + )} From f4994f090e55db2fa91b3efd195329849f31f9db Mon Sep 17 00:00:00 2001 From: sultanofcardio Date: Mon, 14 Oct 2024 11:14:52 -0500 Subject: [PATCH 15/15] Wrap label in StyledBodyLarge --- .../Applet/Popups/DuplicatePopups/DuplicatePopups.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx index 54485aeac..1b77e6754 100644 --- a/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx +++ b/src/modules/Dashboard/features/Applet/Popups/DuplicatePopups/DuplicatePopups.tsx @@ -15,7 +15,13 @@ import { } from 'shared/utils'; import { Modal, Spinner, SpinnerUiType } from 'shared/components'; import { CheckboxController, InputController } from 'shared/components/FormComponents'; -import { StyledErrorText, StyledFlexColumn, StyledModalWrapper, variables } from 'shared/styles'; +import { + StyledBodyLarge, + StyledErrorText, + StyledFlexColumn, + StyledModalWrapper, + variables, +} from 'shared/styles'; import { useAsync } from 'shared/hooks/useAsync'; import { useAppletPrivateKeySetter } from 'modules/Builder/hooks'; import { applet, auth, banners, popups, SingleApplet, workspaces } from 'redux/modules'; @@ -330,7 +336,7 @@ export const DuplicatePopups = ({ onCloseCallback }: { onCloseCallback?: () => v {t('duplicateAppletReportServer')}} sxLabelProps={{ ml: 0 }} sx={{ pl: 0 }} />