diff --git a/src/components/commons/LoadingFull/LoadingFull.tsx b/src/components/commons/LoadingFull/LoadingFull.tsx index d8acf695..38fefc82 100644 --- a/src/components/commons/LoadingFull/LoadingFull.tsx +++ b/src/components/commons/LoadingFull/LoadingFull.tsx @@ -7,7 +7,7 @@ import { makeStyles } from "tss-react/mui"; import { ReactComponent as PowerSettingsIcon } from "assets/illustration/mui-icon/power-settings.svg"; import { useCallback } from "react"; import { lunaticDatabase } from "service/lunatic-database"; -import { logout } from "service/api-service"; +import { logout } from "service/auth-service"; interface LoadingFullProps { message: string; diff --git a/src/pages/end-survey/EndSurvey.tsx b/src/pages/end-survey/EndSurvey.tsx index 75bc8934..40942b75 100644 --- a/src/pages/end-survey/EndSurvey.tsx +++ b/src/pages/end-survey/EndSurvey.tsx @@ -18,7 +18,7 @@ import { SetStateAction, useCallback, useEffect, useState } from "react"; import { isMobile } from "react-device-detect"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate, useOutletContext } from "react-router-dom"; -import { remotePutSurveyData, remotePutSurveyDataReviewer } from "service/api-service"; +import { remotePutSurveyData, remotePutSurveyDataReviewer } from "service/api-service/putRemoteData"; import { getFlatLocalStorageValue } from "service/local-storage-service"; import { getNavigatePath, diff --git a/src/pages/home-surveyed/HomeSurveyed.tsx b/src/pages/home-surveyed/HomeSurveyed.tsx index f2d9846c..bb7fdaca 100644 --- a/src/pages/home-surveyed/HomeSurveyed.tsx +++ b/src/pages/home-surveyed/HomeSurveyed.tsx @@ -28,7 +28,7 @@ import React, { useCallback, useEffect } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; -import { logout } from "service/api-service"; +import { logout, logoutClearData } from "service/auth-service"; import { lunaticDatabase } from "service/lunatic-database"; import { getNavigatePath, setEnviro } from "service/navigation-service"; import { diff --git a/src/service/api-service.ts b/src/service/api-service/getRemoteData.ts similarity index 59% rename from src/service/api-service.ts rename to src/service/api-service/getRemoteData.ts index fb1bf415..fad9ee86 100644 --- a/src/service/api-service.ts +++ b/src/service/api-service/getRemoteData.ts @@ -2,13 +2,12 @@ import { NomenclatureActivityOption } from "@inseefrlab/lunatic-edt"; import axios from "axios"; import { ErrorCodeEnum } from "enumerations/ErrorCodeEnum"; import { ReferentielsEnum } from "enumerations/ReferentielsEnum"; -import { StateDataStateEnum } from "enumerations/StateDataStateEnum"; import { StateData, SurveyData, UserSurveys } from "interface/entity/Api"; import { LunaticData, ReferentielData, SourceData } from "interface/lunatic/Lunatic"; -import jwt, { JwtPayload } from "jwt-decode"; -import { AuthContextProps, User } from "oidc-react"; -import { initStateData, initSurveyData } from "./survey-service"; -import { getAuth, getUserToken, isReviewer } from "./user-service"; + +import { AuthContextProps } from "oidc-react"; +import { initStateData, initSurveyData } from "../survey-service"; +import { getAuth, getUserToken, isReviewer } from "../user-service"; export const edtOrganisationApiBaseUrl = process.env.REACT_APP_EDT_ORGANISATION_API_BASE_URL; export const stromaeBackOfficeApiBaseUrl = process.env.REACT_APP_STROMAE_BACK_OFFICE_API_BASE_URL; @@ -26,48 +25,6 @@ axios.interceptors.response.use( }, ); -//TODO: fix any -const transformCollectedArray = (dataAct: any) => { - console.log("dataAct before transformation", dataAct); - for (const key in dataAct) { - const collected = dataAct[key]?.COLLECTED; - //console.log("collected", collected); - if (Array.isArray(collected)) { - dataAct[key].COLLECTED = collected.map((item: string) => { - if (item && typeof item === "string" && /^\d/.test(item)) { - console.log("item to be modified", item); - return `S${item}`; - } - //console.log("item", item); - return item; - }); - } else if (typeof collected === "string" && /^\d/.test(collected)) { - console.log("collected is a string", collected); - dataAct[key].COLLECTED = `S_${collected}`; - } - } - return dataAct; -}; - -const revertTransformedArray = (dataAct: any) => { - for (const key in dataAct) { - const collected = dataAct[key]?.COLLECTED; - if (Array.isArray(collected)) { - dataAct[key].COLLECTED = collected.map((item: string) => { - if (item && typeof item === "string" && /^S\d/.test(item)) { - console.log("item to be reverted", item); - return item.substring(1); - } - return item; - }); - } else if (typeof collected === "string" && collected.startsWith("S_")) { - console.log("collected is a string to be reverted", collected); - dataAct[key].COLLECTED = collected.substring(1); - } - } - return dataAct; -}; - export const getHeader = (origin?: string, userToken?: string) => { return { headers: { @@ -78,6 +35,15 @@ export const getHeader = (origin?: string, userToken?: string) => { }; }; +const revertTransformedArray = (dataAct: any) => { + const revertedDataAct: { [key: string]: any } = {}; + Object.keys(dataAct).forEach(key => { + const revertedKey = key.startsWith("S_") ? key.substring(2) : key; + revertedDataAct[revertedKey] = dataAct[key]; + }); + return revertedDataAct; +}; + const fetchReferentiel = (auth: AuthContextProps, idReferentiel: ReferentielsEnum) => { return axios.get( stromaeBackOfficeApiBaseUrl + "api/nomenclature/" + idReferentiel, @@ -205,164 +171,6 @@ const fetchReviewerSurveysAssignments = (setError: (error: ErrorCodeEnum) => voi }); }; -const requestPutSurveyData = ( - idSurvey: string, - data: SurveyData, - token?: string, -): Promise => { - //console.log("data", data); - //const collectedData = transformCollectedArray(data?.data?.COLLECTED) - // const collectedData = data?.data?.COLLECTED; - // if (data.data) { - // data.data.COLLECTED = collectedData; - // } - const stateData = data.stateData; - const putLunaticData = axios.put( - `${stromaeBackOfficeApiBaseUrl}api/survey-unit/${idSurvey}/data`, - data.data, - getHeader(stromaeBackOfficeApiBaseUrl, token), - ); - - const putStateData = axios.put( - `${stromaeBackOfficeApiBaseUrl}api/survey-unit/${idSurvey}/state-data`, - stateData, - getHeader(stromaeBackOfficeApiBaseUrl, token), - ); - - return Promise.all([putLunaticData, putStateData]) - .then(() => { - return data; - }) - .catch(error => { - throw error; - }); -}; - -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 - if (!tokenExpiresAt || tokenExpiresAt * 1000 < now.getTime()) { - let auth = getAuth(); - return auth.userManager - .signinSilent() - .then((user: User | null) => { - return requestPutSurveyData(idSurvey, data, user?.access_token); - }) - .catch(err => { - logout(); - return Promise.reject(err); - }); - } else { - return requestPutSurveyData(idSurvey, data); - } -}; - -const remotePutSurveyDataReviewer = ( - idSurvey: string, - stateData: StateData, - data: LunaticData, -): Promise => { - //Temporar check on token validity to avoid 401 error, if not valid, reload page - //# - const now = new Date(); - const tokenExpiresAt = jwt(getUserToken() ?? "").exp; - // * 1000 because tokenExpiresAt is in seconds and now.getTime() in milliseconds - if (!tokenExpiresAt || tokenExpiresAt * 1000 < now.getTime()) { - let auth = getAuth(); - return auth.userManager - .signinSilent() - .then((user: User | null) => { - return requestPutSurveyDataReviewer(idSurvey, data, stateData, user?.access_token); - }) - .catch(err => { - logout(); - return Promise.reject(err); - }); - } else { - return requestPutSurveyDataReviewer(idSurvey, data, stateData); - } -}; - -const requestPutDataReviewer = ( - idSurvey: string, - data: LunaticData, - token?: string, -): Promise => { - const lunaticData: LunaticData = token ? transformCollectedArray(data?.COLLECTED) : data?.COLLECTED; - return new Promise(resolve => { - axios - .put( - stromaeBackOfficeApiBaseUrl + "api/survey-unit/" + idSurvey + "/data", - lunaticData, - getHeader(stromaeBackOfficeApiBaseUrl, token), - ) - .then(() => { - return resolve(data); - }); - }); -}; - -const requestPutStateReviewer = ( - idSurvey: string, - data: StateData, - token?: string, -): Promise => { - return new Promise(resolve => { - axios - .put( - stromaeBackOfficeApiBaseUrl + "api/survey-unit/" + idSurvey + "/state-data", - data, - getHeader(stromaeBackOfficeApiBaseUrl, token), - ) - .then(() => { - return resolve(data); - }) - .catch(err => { - if (err.response?.status == 404) { - const stateData = { - state: StateDataStateEnum.INIT, - date: Date.now(), - currentPage: 0, - }; - return resolve(stateData); - } - }); - }); -}; - -const requestPutSurveyDataReviewer = ( - idSurvey: string, - data: LunaticData, - stateData: StateData, - token?: string, -): Promise => { - return requestPutDataReviewer(idSurvey, data, token).then(() => { - requestPutStateReviewer(idSurvey, stateData, token); - const surveyData: SurveyData = { - stateData: stateData, - data: data, - }; - return surveyData; - }); -}; - -const logout = () => { - let auth = getAuth(); - auth.userManager - .signoutRedirect({ - id_token_hint: localStorage.getItem("id_token") ?? undefined, - }) - .then(() => auth.userManager.clearStaleState()) - .then(() => auth.userManager.signoutRedirectCallback()) - .then(() => { - sessionStorage.clear(); - localStorage.clear(); - }) - .then(() => auth.userManager.clearStaleState()) - .then(() => window.location.replace(process.env.REACT_APP_PUBLIC_URL ?? "")); -}; - const remoteGetSurveyData = ( idSurvey: string, setError?: (error: ErrorCodeEnum) => void, @@ -374,7 +182,6 @@ const remoteGetSurveyData = ( getHeader(stromaeBackOfficeApiBaseUrl), ) .then(response => { - console.log("response.data", response.data); if (response.data.COLLECTED != null) { const revertedTranformedData = revertTransformedArray(response.data.COLLECTED); response.data.COLLECTED = revertedTranformedData; @@ -404,7 +211,6 @@ const requestGetDataReviewer = ( ) .then(response => { if (response.data != null) { - console.log("response.data requestGetDataReviewer", response.data); const revertedTranformedData = revertTransformedArray(response.data.COLLECTED); response.data.COLLECTED = revertedTranformedData; resolve(response.data); @@ -497,9 +303,6 @@ export { fetchReviewerSurveysAssignments, fetchSurveysSourcesByIds, fetchUserSurveysInfo, - logout, remoteGetSurveyData, remoteGetSurveyDataReviewer, - remotePutSurveyData, - remotePutSurveyDataReviewer, }; diff --git a/src/service/api-service/putRemoteData.ts b/src/service/api-service/putRemoteData.ts new file mode 100644 index 00000000..e97650d9 --- /dev/null +++ b/src/service/api-service/putRemoteData.ts @@ -0,0 +1,171 @@ +//TODO: fix any + +import axios from "axios"; +import { StateDataStateEnum } from "enumerations/StateDataStateEnum"; +import { SurveyData, StateData } from "interface/entity/Api"; +import { LunaticData } from "interface/lunatic/Lunatic"; +import { User } from "oidc-react"; +import { getUserToken, getAuth } from "service/user-service"; +import { stromaeBackOfficeApiBaseUrl, getHeader } from "./getRemoteData"; +import jwt, { JwtPayload } from "jwt-decode"; +import { logout, logoutClearData } from "service/auth-service"; + +const transformCollectedArray = (dataAct: any) => { + const transformedDataAct: { [key: string]: any } = {}; + Object.keys(dataAct).forEach(key => { + const transformedKey: string = /^\d/.test(key) ? `S_${key}` : key; + transformedDataAct[transformedKey] = dataAct[key]; + }); + return transformedDataAct; +}; + +const requestPutSurveyData = ( + idSurvey: string, + data: SurveyData, + token?: string, +): Promise => { + //console.log("data", data); + //const collectedData = transformCollectedArray(data?.data?.COLLECTED) + // const collectedData = data?.data?.COLLECTED; + // if (data.data) { + // data.data.COLLECTED = collectedData; + // } + const stateData = data.stateData; + const putLunaticData = axios.put( + `${stromaeBackOfficeApiBaseUrl}api/survey-unit/${idSurvey}/data`, + data.data, + getHeader(stromaeBackOfficeApiBaseUrl, token), + ); + + const putStateData = axios.put( + `${stromaeBackOfficeApiBaseUrl}api/survey-unit/${idSurvey}/state-data`, + stateData, + getHeader(stromaeBackOfficeApiBaseUrl, token), + ); + + return Promise.all([putLunaticData, putStateData]) + .then(() => { + return data; + }) + .catch(error => { + throw error; + }); +}; + +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 + if (!tokenExpiresAt || tokenExpiresAt * 1000 < now.getTime()) { + let auth = getAuth(); + return auth.userManager + .signinSilent() + .then((user: User | null) => { + return requestPutSurveyData(idSurvey, data, user?.access_token); + }) + .catch(err => { + logout(); + return Promise.reject(err); + }); + } else { + return requestPutSurveyData(idSurvey, data); + } +}; + +const remotePutSurveyDataReviewer = ( + idSurvey: string, + stateData: StateData, + data: LunaticData, +): Promise => { + //Temporar check on token validity to avoid 401 error, if not valid, reload page + //# + const now = new Date(); + const tokenExpiresAt = jwt(getUserToken() ?? "").exp; + // * 1000 because tokenExpiresAt is in seconds and now.getTime() in milliseconds + if (!tokenExpiresAt || tokenExpiresAt * 1000 < now.getTime()) { + let auth = getAuth(); + return auth.userManager + .signinSilent() + .then((user: User | null) => { + return requestPutSurveyDataReviewer(idSurvey, data, stateData, user?.access_token); + }) + .catch(err => { + logout(); + return Promise.reject(err); + }); + } else { + return requestPutSurveyDataReviewer(idSurvey, data, stateData); + } +}; + +const requestPutDataReviewer = ( + idSurvey: string, + data: LunaticData, + token?: string, +): Promise => { + return new Promise(resolve => { + axios + .put( + stromaeBackOfficeApiBaseUrl + "api/survey-unit/" + idSurvey + "/data", + data, + getHeader(stromaeBackOfficeApiBaseUrl, token), + ) + .then(() => { + return resolve(data); + }); + }); +}; + +const requestPutStateReviewer = ( + idSurvey: string, + data: StateData, + token?: string, +): Promise => { + return new Promise(resolve => { + axios + .put( + stromaeBackOfficeApiBaseUrl + "api/survey-unit/" + idSurvey + "/state-data", + data, + getHeader(stromaeBackOfficeApiBaseUrl, token), + ) + .then(() => { + return resolve(data); + }) + .catch(err => { + if (err.response?.status == 404) { + const stateData = { + state: StateDataStateEnum.INIT, + date: Date.now(), + currentPage: 0, + }; + return resolve(stateData); + } + }); + }); +}; + +const requestPutSurveyDataReviewer = ( + idSurvey: string, + data: LunaticData, + stateData: StateData, + token?: string, +): Promise => { + return requestPutDataReviewer(idSurvey, data, token).then(() => { + requestPutStateReviewer(idSurvey, stateData, token); + const surveyData: SurveyData = { + stateData: stateData, + data: data, + }; + return surveyData; + }); +}; + +export { + remotePutSurveyData, + remotePutSurveyDataReviewer, + requestPutSurveyData, + requestPutDataReviewer, + requestPutStateReviewer, + requestPutSurveyDataReviewer, + transformCollectedArray, +}; diff --git a/src/service/auth-service.ts b/src/service/auth-service.ts index ee1c5d19..edb839a7 100644 --- a/src/service/auth-service.ts +++ b/src/service/auth-service.ts @@ -1,6 +1,6 @@ import { WebStorageStateStore } from "oidc-client-ts"; import { UserManager } from "oidc-react"; -import { setUserToken } from "./user-service"; +import { getAuth, setUserToken } from "./user-service"; const url = process.env.REACT_APP_KEYCLOAK_AUTHORITY ?? ""; const clientId = process.env.REACT_APP_KEYCLOAK_CLIENT_ID ?? ""; @@ -125,7 +125,7 @@ const signinSilentCallback = (userManager: UserManager) => { userManager.signinSilentCallback(); }; -const logout = (userManager: UserManager) => { +const logoutClearData = (userManager: UserManager) => { userManager.signoutRedirect({ id_token_hint: localStorage.getItem("id_token") ?? "", }); @@ -140,11 +140,28 @@ const signoutRedirectCallback = (userManager: UserManager) => { userManager.clearStaleState(); }; +const logout = () => { + let auth = getAuth(); + auth.userManager + .signoutRedirect({ + id_token_hint: localStorage.getItem("id_token") ?? undefined, + }) + .then(() => auth.userManager.clearStaleState()) + .then(() => auth.userManager.signoutRedirectCallback()) + .then(() => { + sessionStorage.clear(); + localStorage.clear(); + }) + .then(() => auth.userManager.clearStaleState()) + .then(() => window.location.replace(process.env.REACT_APP_PUBLIC_URL ?? "")); +}; + export { createUserManager, getUser, isAuthenticated, isSSO, + logoutClearData, logout, signinRedirect, signinSilent, diff --git a/src/service/navigation-service.ts b/src/service/navigation-service.ts index 5aa43bcf..908bafa3 100644 --- a/src/service/navigation-service.ts +++ b/src/service/navigation-service.ts @@ -343,7 +343,7 @@ const setNamesOfGroup = ( const propsWorkTime = () => { const source = getSource(SourcesEnum.WORK_TIME_SURVEY); const bindingDependenciesOfComponent = source.components.map( - component => component.bindingDependencies ?? [], + (component: any) => component.bindingDependencies ?? [], ); // array of arrays to array const bindingDependencies = ([] as string[]).concat(...bindingDependenciesOfComponent); diff --git a/src/service/survey-service.ts b/src/service/survey-service.ts index a59e3c24..8152ca4e 100644 --- a/src/service/survey-service.ts +++ b/src/service/survey-service.ts @@ -47,7 +47,7 @@ import { import { AuthContextProps } from "oidc-react"; import { Dispatch, SetStateAction } from "react"; import { NavigateFunction } from "react-router-dom"; -import { fetchReviewerSurveysAssignments, remotePutSurveyData } from "service/api-service"; +import { fetchReviewerSurveysAssignments } from "service/api-service/getRemoteData"; import { lunaticDatabase } from "service/lunatic-database"; import { LABEL_WORK_TIME_SURVEY, getCurrentPageSource } from "service/orchestrator-service"; import dataEmptyActivity from "utils/dataEmptyActivity.json"; @@ -69,8 +69,7 @@ import { fetchUserSurveysInfo, remoteGetSurveyData, remoteGetSurveyDataReviewer, - remotePutSurveyDataReviewer, -} from "./api-service"; +} from "./api-service/getRemoteData"; import { getFlatLocalStorageValue } from "./local-storage-service"; import { getFullNavigatePath, setAllNamesOfGroupAndNav } from "./navigation-service"; import { getAutoCompleteRef } from "./referentiel-service"; @@ -78,6 +77,7 @@ import { CreateIndex, optionsFiltered, setIndexSuggester } from "./suggester-ser import { getQualityScore } from "./summary-service"; import { getActivitiesOrRoutes, getScore } from "./survey-activity-service"; import { getUserRights, isReviewer } from "./user-service"; +import { remotePutSurveyData, remotePutSurveyDataReviewer } from "./api-service/putRemoteData"; const datas = new Map(); const oldDatas = new Map(); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 5a2fb90a..1f42a2e1 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,6 +2,7 @@ import dayjs from "dayjs"; import customParseFormat from "dayjs/plugin/customParseFormat"; import { EdtRoutesNameEnum } from "enumerations/EdtRoutesNameEnum"; import { OrchestratorContext } from "interface/lunatic/Lunatic"; +import { isArray, isEqual, isObject, transform } from "lodash"; import { isAndroid, isChrome, @@ -153,6 +154,52 @@ function formatDate(input: string) { return inputFormatted; } +//TODO: Test function to be removed +function difference(origObj: any, newObj: any) { + function changes(newObj: any, origObj: { [x: string]: any }) { + let arrayIndexCounter = 0; + return transform( + newObj, + function (result: { [x: string]: any }, value: any, key: string | number) { + if (!isEqual(value, origObj[key])) { + let resultKey = isArray(origObj) ? arrayIndexCounter++ : key; + result[resultKey] = + isObject(value) && isObject(origObj[key]) ? changes(value, origObj[key]) : value; + } + }, + ); + } + console.log("Difference : ", changes(newObj, origObj)); + return changes(newObj, origObj); +} + +function mergeObjects(origObj: any, newObj: any): any { + // Validate input objects + if (!origObj || !newObj || typeof origObj !== "object" || typeof newObj !== "object") { + throw new Error("Both origObj and newObj must be valid objects."); + } + + // Fields to merge + const fieldsToMerge = ["variables", "components"]; + + fieldsToMerge.forEach(field => { + // Check if the field exists and is an array in origObj + if (Array.isArray(origObj[field])) { + // Ensure the field exists in newObj; if not, initialize as an empty array + newObj[field] = newObj[field] || []; + + // Merge the array from origObj into newObj without duplicates + origObj[field].forEach((item: any) => { + if (!newObj[field].some((newItem: any) => isEqual(newItem, item))) { + newObj[field].push(item); + } + }); + } + }); + + return newObj; +} + export { addArrayToSession, addItemToSession, @@ -170,4 +217,6 @@ export { isAndroidNav, objectEquals, sumAllOfArray, + difference, + mergeObjects, };