diff --git a/.env.development b/.env.development index f14235d5..8dc1b322 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,5 @@ -REACT_APP_STROMAE_BACK_OFFICE_API_BASE_URL=https://stromae-edt-kc.demo.insee.io/ -REACT_APP_EDT_ORGANISATION_API_BASE_URL=https://edt-api-kc.demo.insee.io/ +REACT_APP_STROMAE_BACK_OFFICE_API_BASE_URL=https://edt-stromae-v2-api.demo.insee.io/ +REACT_APP_EDT_ORGANISATION_API_BASE_URL=https://edt-api.demo.insee.io/ REACT_APP_KEYCLOAK_AUTHORITY=https://auth.demo.insee.io/auth/realms/questionnaires-edt/ REACT_APP_KEYCLOAK_CLIENT_ID=client-edt REACT_APP_KEYCLOAK_REDIRECT_URI=http://localhost:3000/ @@ -10,4 +10,4 @@ REACT_APP_CHROMIUM_PATH=node_modules/chromium/lib/chromium/chrome-win/chrome.exe REACT_APP_NUM_ACTIVITY_SURVEYS=6 REACT_APP_NUM_WORKTIME_SURVEYS=3 REACT_APP_REVIEWER_ROLE=edt-reviewer -REACT_APP_SURVEYED_ROLE=repondant_coleman \ No newline at end of file +REACT_APP_SURVEYED_ROLE=repondant_coleman diff --git a/package.json b/package.json index c7ad5846..be5e876d 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "typescript": "^4.8.3" }, "scripts": { - "start": "react-scripts start", + "start": "NODE_OPTIONS=--openssl-legacy-provider react-scripts start", "build": "react-scripts --max_old_space_size=4096 build", "build:staging": "env-cmd -f .env.staging react-scripts --max_old_space_size=4096 build", "build:production": "env-cmd -f .env.production react-scripts --max_old_space_size=4096 build", diff --git a/src/pages/activity/activity-or-route-planner/ActivityOrRoutePlanner.tsx b/src/pages/activity/activity-or-route-planner/ActivityOrRoutePlanner.tsx index c301d288..df654d97 100644 --- a/src/pages/activity/activity-or-route-planner/ActivityOrRoutePlanner.tsx +++ b/src/pages/activity/activity-or-route-planner/ActivityOrRoutePlanner.tsx @@ -163,7 +163,7 @@ const onFinish = ( ) => { if (closed) { const data = setValue(idSurvey, FieldNameEnum.ISCLOSED, true); - saveData(idSurvey, data ?? callbackHolder.getData(), false, true).then(() => { + saveData(idSurvey, { ...data, ...callbackHolder.getData() }, false, true).then(() => { navigate( getCurrentNavigatePath( idSurvey, diff --git a/src/pages/activity/activity-or-route-planner/activity-duration/ActivityDuration.tsx b/src/pages/activity/activity-or-route-planner/activity-duration/ActivityDuration.tsx index a504b599..d1097036 100644 --- a/src/pages/activity/activity-or-route-planner/activity-duration/ActivityDuration.tsx +++ b/src/pages/activity/activity-or-route-planner/activity-duration/ActivityDuration.tsx @@ -150,7 +150,7 @@ const ActivityDurationPage = () => { } if ((skip && isAfter) || !isAfter) { - saveDataLocally(idSurvey, callbackHolder.getData()).then(() => { + saveDataLocally(idSurvey, { ...context.data, ...callbackHolder.getData() }).then(() => { navigate( getLoopParameterizedNavigatePath( idSurvey, diff --git a/src/pages/end-survey/EndSurvey.tsx b/src/pages/end-survey/EndSurvey.tsx index c06f2356..418aea82 100644 --- a/src/pages/end-survey/EndSurvey.tsx +++ b/src/pages/end-survey/EndSurvey.tsx @@ -109,17 +109,12 @@ const EndSurveyPage = () => { const handleError = () => { setErrorSubmit(true); }; - if (!isDemoMode && navigator.onLine) { - if (isReviewer()) { - return remotePutSurveyDataReviewer(idSurvey, stateData, surveyData.data) - .then(navToHome) - .catch(handleError); - } else { - return remotePutSurveyData(idSurvey, surveyData).then(navToHome).catch(handleError); - } - } else { + if (isDemoMode) { return saveDataAndInit(surveyData, true); } + saveData(idSurvey, { ...surveyData.data, stateData: stateData }, false, true) + .then(navToHome) + .catch(handleError); }, []); const onPrevious = useCallback(() => { diff --git a/src/service/api-service/getRemoteData.ts b/src/service/api-service/getRemoteData.ts index 96c51139..515ebdfc 100644 --- a/src/service/api-service/getRemoteData.ts +++ b/src/service/api-service/getRemoteData.ts @@ -4,8 +4,6 @@ import { StateData, SurveyData, UserSurveys } from "interface/entity/Api"; import { LunaticData, ReferentielData, SourceData } from "interface/lunatic/Lunatic"; import { initStateData, initSurveyData } from "../survey-service"; import { getUserToken, isReviewer } from "../user-service"; -import { AuthContextProps } from "oidc-react"; -import { NomenclatureActivityOption } from "@inseefrlab/lunatic-edt"; import { ReferentielsEnum } from "enumerations/ReferentielsEnum"; import { revertTransformedArray } from "utils/utils"; @@ -35,14 +33,9 @@ export const getHeader = (origin?: string, userToken?: string) => { }; }; -const fetchRemoteReferentiel = (auth: AuthContextProps, idReferentiel: ReferentielsEnum) => { - return axios.get( - stromaeBackOfficeApiBaseUrl + "api/nomenclature/" + idReferentiel, - getHeader(stromaeBackOfficeApiBaseUrl), - ); -}; - -const fetchRemoteReferentiels = (setError: (error: ErrorCodeEnum) => void): Promise => { +export const fetchRemoteReferentiels = ( + setError: (error: ErrorCodeEnum) => void, +): Promise => { let refs: ReferentielData = { [ReferentielsEnum.ACTIVITYNOMENCLATURE]: [], [ReferentielsEnum.ACTIVITYAUTOCOMPLETE]: [], @@ -86,7 +79,9 @@ const fetchRemoteReferentiels = (setError: (error: ErrorCodeEnum) => void): Prom }); }; -const fetchUserSurveysInfo = (setError: (error: ErrorCodeEnum) => void): Promise => { +export const fetchUserSurveysInfo = ( + setError: (error: ErrorCodeEnum) => void, +): Promise => { return new Promise(resolve => { axios .get( @@ -107,7 +102,7 @@ const fetchUserSurveysInfo = (setError: (error: ErrorCodeEnum) => void): Promise }); }; -const fetchSurveysSourcesByIds = ( +export const fetchSurveysSourcesByIds = ( sourcesIds: string[], setError: (error: ErrorCodeEnum) => void, ): Promise => { @@ -140,7 +135,9 @@ const fetchSurveysSourcesByIds = ( }); }; -const fetchReviewerSurveysAssignments = (setError: (error: ErrorCodeEnum) => void): Promise => { +export const fetchReviewerSurveysAssignments = ( + setError: (error: ErrorCodeEnum) => void, +): Promise => { return new Promise(resolve => { axios .get( @@ -160,7 +157,7 @@ const fetchReviewerSurveysAssignments = (setError: (error: ErrorCodeEnum) => voi }); }; -const remoteGetSurveyData = ( +export const remoteGetSurveyData = ( idSurvey: string, setError?: (error: ErrorCodeEnum) => void, ): Promise => { @@ -192,7 +189,7 @@ const remoteGetSurveyData = ( }); }; -const remoteGetSurveyStateData = ( +export const remoteGetSurveyStateData = ( idSurvey: string, setError?: (error: ErrorCodeEnum) => void, ): Promise => { @@ -215,7 +212,7 @@ const remoteGetSurveyStateData = ( }); }); }; -const remoteGetSurveyDataSurveyed = ( +export const remoteGetSurveyDataSurveyed = ( idSurvey: string, setError: (error: ErrorCodeEnum) => void, ): Promise => { @@ -232,7 +229,7 @@ const remoteGetSurveyDataSurveyed = ( }); }; -const requestGetDataReviewer = ( +export const requestGetDataReviewer = ( idSurvey: string, setError: (error: ErrorCodeEnum) => void, ): Promise => { @@ -269,7 +266,7 @@ const requestGetDataReviewer = ( }); }; -const requestGetSurveyDataReviewer = ( +export const requestGetSurveyDataReviewer = ( idSurvey: string, setError: (error: ErrorCodeEnum) => void, ): Promise => { @@ -286,7 +283,7 @@ const requestGetSurveyDataReviewer = ( }); }; -const remoteGetSurveyDataReviewer = ( +export const remoteGetSurveyDataReviewer = ( idSurvey: string, setError: (error: ErrorCodeEnum) => void, ): Promise => { @@ -308,16 +305,3 @@ const remoteGetSurveyDataReviewer = ( return Promise.reject(err); }); }; - -export { - fetchRemoteReferentiel, - fetchRemoteReferentiels, - fetchReviewerSurveysAssignments, - fetchSurveysSourcesByIds, - fetchUserSurveysInfo, - remoteGetSurveyData, - requestGetDataReviewer, - remoteGetSurveyStateData, - remoteGetSurveyDataReviewer, - remoteGetSurveyDataSurveyed, -}; diff --git a/src/service/api-service/putRemoteData.ts b/src/service/api-service/putRemoteData.ts index b20a6e04..01c2ab91 100644 --- a/src/service/api-service/putRemoteData.ts +++ b/src/service/api-service/putRemoteData.ts @@ -9,7 +9,7 @@ import jwt, { JwtPayload } from "jwt-decode"; import { logout } from "service/auth-service"; import { transformCollectedArray } from "utils/utils"; -const requestPutSurveyData = ( +export const requestPutSurveyData = ( idSurvey: string, data: SurveyData, token?: string, @@ -44,7 +44,7 @@ const requestPutSurveyData = ( }); }; -const remotePutSurveyData = (idSurvey: string, data: SurveyData): Promise => { +export const remotePutSurveyData = (idSurvey: string, data: SurveyData): Promise => { const now = new Date(); const tokenExpiresAt = jwt(getUserToken() ?? "").exp; // * 1000 because tokenExpiresAt is in seconds and now.getTime() in milliseconds @@ -64,7 +64,7 @@ const remotePutSurveyData = (idSurvey: string, data: SurveyData): Promise; } -class LunaticDatabaseImpl extends Dexie implements LunaticDatabase { +/** + * Database implementation that stores data in IndexedDB using Dexie + */ +class LunaticIndexedDB extends Dexie implements LunaticDatabase { lunaticData!: Dexie.Table; public constructor() { @@ -31,7 +34,10 @@ class LunaticDatabaseImpl extends Dexie implements LunaticDatabase { } } -class MemoryLunaticDatabaseImpl implements LunaticDatabase { +/** + * In-Memory database implementation, used as a fallback if Dexie fails to initialize + */ +class LunaticMemoryDB implements LunaticDatabase { lunaticData = new Map(); public save(id: string, data: LunaticData): Promise { @@ -49,35 +55,39 @@ class MemoryLunaticDatabaseImpl implements LunaticDatabase { } } -class PromiseProxyLunaticDatabaseImpl implements LunaticDatabase { - lunaticDatabasePromise = new Map(); - constructor(private promise: Promise) {} +/** + * Proxy the calls to the right database implementation according to browser support + */ +class BrowserDatabase implements LunaticDatabase { + private db: Promise; + + constructor() { + this.db = new Promise(resolve => { + const database = new LunaticIndexedDB(); + database + .get("") + .then(() => resolve(database)) + .catch(e => { + console.warn( + "- Dexie will not work in this environment. We will use a memory database.", + ); + console.debug(e); + resolve(new LunaticMemoryDB()); + }); + }); + } public save(id: string, data: LunaticData): Promise { - return lunaticDatabasePromise.then(database => database.save(id, data)); + return this.db.then(database => database.save(id, data)); } public get(id: string): Promise { - return lunaticDatabasePromise.then(database => database.get(id)); + return this.db.then(database => database.get(id)); } public clear(): Promise { - return lunaticDatabasePromise.then(database => database.clear()); + return this.db.then(database => database.clear()); } } -// this is somewhat complexe as browser sometime cannot work with dexie in private mode -export const lunaticDatabasePromise = new Promise(resolve => { - // validate dexie is working on this computer - const database = new LunaticDatabaseImpl(); - database - .get("") - .then(() => resolve(database)) - .catch(e => { - console.warn("- Dexie will not work in this environment. We will use a memory database."); - console.debug(e); - resolve(new MemoryLunaticDatabaseImpl()); - }); -}); - -export const lunaticDatabase = new PromiseProxyLunaticDatabaseImpl(lunaticDatabasePromise); +export const lunaticDatabase = new BrowserDatabase(); diff --git a/src/service/navigation-service.ts b/src/service/navigation-service.ts index 274d05fd..3daa72fe 100644 --- a/src/service/navigation-service.ts +++ b/src/service/navigation-service.ts @@ -35,6 +35,9 @@ let _context: OrchestratorContext; let _navigate: NavigateFunction; let _callbackHolder: { getData(): LunaticData; getErrors(): { [key: string]: [] } }; +/** + * Make context and navigation globally available + */ const setEnviro = ( context: OrchestratorContext, navigate: NavigateFunction, @@ -49,6 +52,10 @@ const getNavigatePath = (page: EdtRoutesNameEnum): string => { return "/" + page; }; +/** + * Add a parameter at the end of a path containing a dynamic parameter + * For instance `/code/:code` will become `/code/3` + */ const getParameterizedNavigatePath = (page: EdtRoutesNameEnum, param: string): string => { return "/" + page.split(":")[0] + param; }; @@ -207,11 +214,19 @@ const saveAndNav = ( routeNotSelection?: string, currentIteration?: number, ): void => { - saveData(idSurvey, _callbackHolder.getData() ?? getData(idSurvey)).then(() => { + saveData(idSurvey, { ...getData(idSurvey), ..._callbackHolder.getData() }).then(() => { navToRouteOrRouteNotSelection(idSurvey, route, value, routeNotSelection, currentIteration); }); + /* + saveData(idSurvey, , {...Data(idSurvey), ..._callbackHolder.getData()})then(() => { + navToRouteOrRouteNotSelection(idSurvey, route, value, routeNotSelection, currentIteration); + }); +*/ }; +/** + * Save data using the callbackHolder and then navigate + */ const saveAndNavLocally = ( idSurvey: string, route?: string, @@ -219,7 +234,7 @@ const saveAndNavLocally = ( routeNotSelection?: string, currentIteration?: number, ): void => { - saveDataLocally(idSurvey, _callbackHolder.getData() ?? getData(idSurvey)).then(() => { + saveDataLocally(idSurvey, { ...getData(idSurvey), ..._callbackHolder.getData() }).then(() => { navToRouteOrRouteNotSelection(idSurvey, route, value, routeNotSelection, currentIteration); }); }; @@ -241,15 +256,21 @@ const closeFormularieAndNav = (idSurvey: string, route: string) => { * we need to make the call twice to be able to retrieve the current state of the database */ const validate = (idSurvey: string): Promise => { - return saveData(idSurvey, _callbackHolder.getData() ?? getData(idSurvey), true).then(() => { - return saveData(idSurvey, _callbackHolder.getData() ?? getData(idSurvey), false); + return saveData(idSurvey, { ...getData(idSurvey), ..._callbackHolder.getData() }, true).then(() => { + return saveData(idSurvey, { ...getData(idSurvey), ..._callbackHolder.getData() }, false); }); }; const validateLocally = (idSurvey: string): Promise => { - return saveDataLocally(idSurvey, _callbackHolder.getData() ?? getData(idSurvey), true).then(() => { - return saveDataLocally(idSurvey, _callbackHolder.getData() ?? getData(idSurvey), false); - }); + return saveDataLocally(idSurvey, { ...getData(idSurvey), ..._callbackHolder.getData() }, true).then( + () => { + return saveDataLocally( + idSurvey, + { ...getData(idSurvey), ..._callbackHolder.getData() }, + false, + ); + }, + ); }; const navToRouteOrRouteNotSelection = ( diff --git a/src/service/survey-service.ts b/src/service/survey-service.ts index 2a76057d..afd87442 100644 --- a/src/service/survey-service.ts +++ b/src/service/survey-service.ts @@ -132,17 +132,6 @@ const initializeDatas = (setError: (error: ErrorCodeEnum) => void): Promise void): Promise => { - const promisesToWait: Promise[] = []; - return new Promise(resolve => { - promisesToWait.push(initializeRemoteRefs(setError)); - promisesToWait.push(initializeSurveysIdsAndSources(setError)); - Promise.all(promisesToWait).then(() => { - resolve(true); - }); - }); -}; - const initPropsAuth = (auth: AuthContextProps): Promise => { const dataState: DataState = { data: { @@ -505,6 +494,9 @@ const initializeSurveysIdsDataModeReviewer = ( }); }; +/** + * Create a data object from fetched survey data + */ const initializeData = (remoteSurveyData: any, idSurvey: string) => { const regexp = new RegExp(process.env.REACT_APP_HOUSE_REFERENCE_REGULAR_EXPRESSION || ""); let surveyData: LunaticData = { @@ -525,6 +517,9 @@ const initializeData = (remoteSurveyData: any, idSurvey: string) => { return surveyData; }; +/** + * Fetch survey data from the server + */ const getRemoteSavedSurveyData = ( surveyId: string, setError: (error: ErrorCodeEnum) => void, @@ -535,6 +530,7 @@ const getRemoteSavedSurveyData = ( const getSurveyDataFunction = isReviewer() ? requestGetDataReviewer : remoteGetSurveyData; const getSurveyStateDataFunction = remoteGetSurveyStateData; + //TODO: Refactor dirty code return getSurveyDataFunction(surveyId, setError) .then((remoteSurveyData: any) => { @@ -542,8 +538,7 @@ const getRemoteSavedSurveyData = ( return lunaticDatabase.get(surveyId).then(localSurveyData => { if (shouldInitData(remoteSurveyData, localSurveyData)) { const stateData = getLocalSurveyStateData(surveyData); - setLocalDatabase(stateData, surveyData, surveyId); - return lunaticDatabase.save(surveyId, surveyData); + return saveInDatabase(surveyId, { ...surveyData, stateData }); } else { if (shouldSaveRemoteData(remoteSurveyData, localSurveyData)) { // TEMP: WeeklyPlanner stuff (to be removed) @@ -552,11 +547,9 @@ const getRemoteSavedSurveyData = ( if (weeklyPlanner) { remoteSurveyData.COLLECTED.WEEKLYPLANNER = weeklyPlanner; } - getSurveyStateDataFunction(surveyId, setError).then(stateData => { - setLocalDatabase(stateData, remoteSurveyData, surveyId); + return getSurveyStateDataFunction(surveyId, setError).then(stateData => { + return saveInDatabase(surveyId, { ...remoteSurveyData, stateData }); }); - - return lunaticDatabase.save(surveyId, surveyData); } } }); @@ -571,10 +564,7 @@ const getRemoteSavedSurveysDatas = ( surveysIds: string[], setError: (error: ErrorCodeEnum) => void, ): Promise => { - const promises: Promise[] = surveysIds.map(surveyId => - getRemoteSavedSurveyData(surveyId, setError), - ); - return Promise.all(promises); + return Promise.all(surveysIds.map(surveyId => getRemoteSavedSurveyData(surveyId, setError))); }; const shouldSaveRemoteData = (remoteSurveyData: any, localSurveyData: any): boolean => { @@ -592,12 +582,14 @@ const shouldSaveRemoteData = (remoteSurveyData: any, localSurveyData: any): bool return false; }; +/** + * Detect if collected data is empty + */ const shouldInitData = (remoteSurveyData: any, localSurveyData: any): boolean => { if (!localSurveyData) { if (remoteSurveyData && typeof remoteSurveyData === "object") { const isCollectedEmpty = "COLLECTED" in remoteSurveyData && Object.keys(remoteSurveyData.COLLECTED).length === 0; - //console.log("shouldInitData", isCollectedEmpty); return isCollectedEmpty; } } @@ -875,6 +867,9 @@ const saveDatas = () => { }); }; +/** + * Save data in the local database and push to the server if necessary + */ const saveData = ( idSurvey: string, data: LunaticData, @@ -882,6 +877,11 @@ const saveData = ( forceUpdate = false, stateDataForced?: StateData, ): Promise => { + if (stateDataForced) { + console.error( + "stateDataForced parameter was removed, put state data inside the data object instead", + ); + } data.lastLocalSaveDate = navigator.onLine ? Date.now() : Date.now() + 1; if (!data.houseReference) { const regexp = new RegExp(process.env.REACT_APP_HOUSE_REFERENCE_REGULAR_EXPRESSION || ""); @@ -895,7 +895,7 @@ const saveData = ( const isChange = forceUpdate || dataIsChanged; datas.set(idSurvey, data); data = updateLocked(idSurvey, data); - let stateData: StateData = data?.stateData ?? stateDataForced ?? initStateData(data); + let stateData: StateData = data?.stateData ?? getLocalSurveyStateData(data) ?? initStateData(data); if (!navigator.onLine || isDemoMode || localSaveOnly) stateData.date = 0; @@ -907,7 +907,7 @@ const saveData = ( if (!navigator.onLine) { stateData.date = 0; data.stateData = stateData; - return setLocalDatabase(stateData, data, idSurvey); + return saveInDatabase(idSurvey, data); } if (!isDemoMode && !localSaveOnly) { stateData.date = data.lastLocalSaveDate ?? Date.now(); @@ -922,26 +922,29 @@ const saveData = ( stateData.date = Math.max(stateData.date, data.lastLocalSaveDate ?? 0); data.stateData = stateData; data.lastRemoteSaveDate = stateData.date; - return setLocalDatabase(stateData, data, idSurvey); + return saveInDatabase(idSurvey, data); }); } else { //TODO: TEMP: need to figure out why there is still a state data here return remotePutSurveyData(idSurvey, surveyData).then(() => { data.stateData = stateData; - return setLocalDatabase(stateData, data, idSurvey); + return saveInDatabase(idSurvey, data); }); } } else { stateData.date = 0; data.stateData = stateData; - return setLocalDatabase(stateData, data, idSurvey); + return saveInDatabase(idSurvey, data); } } else { data.stateData = stateData; - return setLocalDatabase(stateData, data, idSurvey); + return saveInDatabase(idSurvey, data); } }; +/** + * @deprecated use saveData with the right parameters instead + */ const saveDataLocally = ( idSurvey: string, data: LunaticData, @@ -949,33 +952,13 @@ const saveDataLocally = ( forceUpdate = false, stateDataForced?: StateData, ): Promise => { - data.lastLocalSaveDate = navigator.onLine ? Date.now() : Date.now() + 1; - if (!data.houseReference) { - const regexp = new RegExp(process.env.REACT_APP_HOUSE_REFERENCE_REGULAR_EXPRESSION || ""); - data.houseReference = idSurvey.replace(regexp, ""); - } - const isDemoMode = getFlatLocalStorageValue(LocalStorageVariableEnum.IS_DEMO_MODE) === "true"; - //const isReviewerMode = getUserRights() == EdtUserRightsEnum.REVIEWER; - fixConditionals(data); - let oldDataSurvey = datas.get(idSurvey) ?? {}; - const dataIsChanged = dataIsChange(idSurvey, data, oldDataSurvey); - const isChange = forceUpdate || dataIsChanged; - datas.set(idSurvey, data); - - data = updateLocked(idSurvey, data); - let stateData: StateData = stateDataForced ?? initStateData(data); - - if (!navigator.onLine || isDemoMode || localSaveOnly) stateData.date = 0; - - if (isChange) { - data = saveQualityScore(idSurvey, data); - } - data.stateData = stateData; - console.log("saveDataLocally"); - return setLocalDatabase(stateData, data, idSurvey); + return saveData(idSurvey, data, true, forceUpdate, stateDataForced); }; -const setLocalDatabase = (stateData: StateData, data: LunaticData, idSurvey: string) => { +/** + * Save the new data in the database and keep the previous data in "oldDatas" variable + */ +const saveInDatabase = (idSurvey: string, data: LunaticData) => { let oldDataSurvey = datas.get(idSurvey) ?? {}; oldDatas.set(idSurvey, oldDataSurvey); setDataCache(idSurvey, data); @@ -1736,7 +1719,6 @@ export { initStateData, initSurveyData, initializeDatas, - initializeRemoteDatas, initializeHomeSurveys, initializeListSurveys, initializeSurveysDatasCache,