diff --git a/src/actions/app-state.jsx b/src/actions/app-state.jsx index 97b337e26..209b4b8c9 100644 --- a/src/actions/app-state.jsx +++ b/src/actions/app-state.jsx @@ -1,4 +1,5 @@ -import { getContextFromCampaign, putQuestionnaire } from '../api/remote-api'; +import { putQuestionnaire } from '../api/questionnaires'; +import { getContextFromCampaign } from '../api/search'; import { getVersion } from '../api/versions'; import { getVisualization } from '../api/visualize'; import { TCM } from '../constants/pogues-constants'; diff --git a/src/actions/metadata.jsx b/src/actions/metadata.jsx index 2b17ed63a..a21cf89e1 100644 --- a/src/actions/metadata.jsx +++ b/src/actions/metadata.jsx @@ -1,13 +1,11 @@ import { getCampaigns, - getNomenclature, - getNomenclatures, getOperations, - getQuestionnaire, getSeries, getUnitsList, - getVariablesById, -} from '../api/remote-api'; +} from '../api/metadata'; +import { getNomenclature, getNomenclatures } from '../api/nomenclatures'; +import { getQuestionnaire, getVariablesById } from '../api/questionnaires'; import { DIMENSION_LENGTH } from '../constants/pogues-constants'; const { NON_DYNAMIC } = DIMENSION_LENGTH; diff --git a/src/actions/questionnaire-list.jsx b/src/actions/questionnaire-list.jsx index 8d5ed4c78..1553599ad 100644 --- a/src/actions/questionnaire-list.jsx +++ b/src/actions/questionnaire-list.jsx @@ -1,4 +1,4 @@ -import { getQuestionnaireList } from '../api/remote-api'; +import { getQuestionnaireList } from '../api/questionnaires'; import { questionnaireListRemoteToStores } from '../model/remote-to-stores'; export const LOAD_QLIST = 'LOAD_QLIST'; diff --git a/src/actions/questionnaire.jsx b/src/actions/questionnaire.jsx index 840b7108c..7b9961950 100644 --- a/src/actions/questionnaire.jsx +++ b/src/actions/questionnaire.jsx @@ -2,7 +2,7 @@ import { deleteQuestionnaire, getQuestionnaire, postQuestionnaire, -} from '../api/remote-api'; +} from '../api/questionnaires'; import { COMPONENT_TYPE } from '../constants/pogues-constants'; import { Component } from '../model'; import { questionnaireRemoteToStores } from '../model/remote-to-stores'; diff --git a/src/actions/search.jsx b/src/actions/search.jsx index 3723e4ff5..ab5edc828 100644 --- a/src/actions/search.jsx +++ b/src/actions/search.jsx @@ -1,4 +1,4 @@ -import { getSearchResults } from '../api/remote-api'; +import { getSearchResults } from '../api/search'; export const LOAD_SEARCH_RESULT = 'LOAD_SEARCH_RESULT'; export const LOAD_SEARCH_RESULT_SUCCESS = 'LOAD_SEARCH_RESULT_SUCCESS'; @@ -47,17 +47,17 @@ export const loadSearchResultFailure = (err) => ({ * Load search results list * @param {string} token The user token. * @param {string} typeItem The type of item to search. - * @param {object} criterias The list of criterias. + * @param {object} criteria The list of criteria. * @param {string} filter The text to filter. * @return {object} LOAD_SEARCH_RESULT action */ export const loadSearchResult = - (token, typeItem, criterias, filter) => (dispatch) => { + (token, typeItem, criteria, filter) => (dispatch) => { dispatch({ type: LOAD_SEARCH_RESULT, payload: null, }); - return getSearchResults(token, typeItem, criterias, filter) + return getSearchResults(token, typeItem, criteria, filter) .then((resultsList) => dispatch(loadSearchResultSuccess(resultsList))) .catch((err) => dispatch(loadSearchResultFailure(err))); }; diff --git a/src/api/init.ts b/src/api/init.ts new file mode 100644 index 000000000..83faade78 --- /dev/null +++ b/src/api/init.ts @@ -0,0 +1,13 @@ +import { getBaseURI } from './utils'; + +type Init = { + isSearchDisable: boolean; +}; + +export async function getInit(): Promise { + const url = `${getBaseURI()}/init`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + + return fetch(url, { headers }).then((res) => res.json() as Promise); +} diff --git a/src/api/metadata.ts b/src/api/metadata.ts new file mode 100644 index 000000000..cee503873 --- /dev/null +++ b/src/api/metadata.ts @@ -0,0 +1,43 @@ +import { computeAuthorizationHeader, getBaseURI } from './utils'; + +export async function getSeries(token: string): Promise { + const url = `${getBaseURI()}/search/series`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { headers }).then((res) => res.json()); +} + +export async function getOperations( + id: string, + token: string, +): Promise { + const url = `${getBaseURI()}/search/series/${id}/operations`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { headers }).then((res) => res.json()); +} + +export async function getCampaigns( + id: string, + token: string, +): Promise { + const url = `${getBaseURI()}/search/operations/${id}/collections`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { headers }).then((res) => res.json()); +} + +export async function getUnitsList(token: string): Promise { + const url = `${getBaseURI()}/meta-data/units`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { headers }).then((res) => res.json()); +} diff --git a/src/api/nomenclatures.ts b/src/api/nomenclatures.ts new file mode 100644 index 000000000..807adf40a --- /dev/null +++ b/src/api/nomenclatures.ts @@ -0,0 +1,15 @@ +import getNomenclaturesContent from '../utils/codes-lists/__mocks__/get-nomenclatures.json'; + +type NomenclaturesJSON = { + nomenclatures: { [key: string]: unknown }; +}; + +const nomenclaturesJSON: NomenclaturesJSON = getNomenclaturesContent; + +export function getNomenclatures(): NomenclaturesJSON { + return nomenclaturesJSON; +} + +export function getNomenclature(id: string): unknown { + return nomenclaturesJSON.nomenclatures[id]; +} diff --git a/src/api/questionnaires.ts b/src/api/questionnaires.ts new file mode 100644 index 000000000..e361250d6 --- /dev/null +++ b/src/api/questionnaires.ts @@ -0,0 +1,117 @@ +import { computeAuthorizationHeader, getBaseURI } from './utils'; + +const pathQuestionnaire = 'persistence/questionnaire'; +const pathQuestionnaireList = 'persistence/questionnaires'; + +/** + * Retrieve questionnaires associated to the provided stamp (e.g. "DR59-SNDI59") + */ +export async function getQuestionnaireList( + stamp: string, + token: string, +): Promise { + const url = `${getBaseURI()}/${pathQuestionnaireList}/search/meta?owner=${stamp}`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { + headers, + }).then((res) => res.json() as Promise); +} + +export async function getStampsList(token: string): Promise { + const url = `${getBaseURI()}/${pathQuestionnaireList}/stamps`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { + headers, + }).then((res) => res.json()); +} + +/** + * Create new questionnaire + */ +export async function postQuestionnaire(qr: unknown, token: string) { + const url = `${getBaseURI()}/${pathQuestionnaireList}`; + const headers = new Headers(); + headers.append('Content-Type', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(qr), + }).then((res) => { + if (res.ok) return res; + throw new Error(`Network request failed :${res.statusText}`); + }); +} + +/** + * Update questionnaire by id + */ +export async function putQuestionnaire(id: string, qr: unknown, token: string) { + const url = `${getBaseURI()}/${pathQuestionnaire}/${id}`; + const headers = new Headers(); + headers.append('Content-Type', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { + method: 'PUT', + headers, + body: JSON.stringify(qr), + }).then((res) => { + if (res.ok) return res; + throw new Error(`Network request failed :${res.statusText}`); + }); +} + +/** + * Retrieve questionnaire by id + */ +export async function getQuestionnaire( + id: string, + token: string, +): Promise { + const url = `${getBaseURI()}/${pathQuestionnaire}/${id}`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { + headers, + }).then((res) => res.json()); +} + +/** + * Will send a DELETE request in order to remove an existing questionnaire + * + * @param {deleteQuestionnaire} id The id of the questionnaire we want to delete + */ +export async function deleteQuestionnaire(id: string, token: string) { + const url = `${getBaseURI()}/${pathQuestionnaire}/${id}`; + const headers = new Headers(); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { + method: 'DELETE', + headers, + }); +} + +export async function getVariablesById( + id: string, + token: string, +): Promise { + const url = `${getBaseURI()}/${pathQuestionnaire}/${id}/variables`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { + headers, + }).then((res) => res.json()); +} diff --git a/src/api/remote-api.js b/src/api/remote-api.js deleted file mode 100644 index 24f4f36fc..000000000 --- a/src/api/remote-api.js +++ /dev/null @@ -1,146 +0,0 @@ -import getNomenclaturesContent from '../utils/codes-lists/__mocks__/get-nomenclatures.json'; -import { getUrlFromCriterias } from '../utils/utils'; -import { getBaseURI } from './utils'; - -const pathInit = 'init'; -const pathQuestionnaireList = 'persistence/questionnaires'; -const pathQuestionnaireListSearch = 'persistence/questionnaires/search/meta'; -const pathQuestionnaire = 'persistence/questionnaire'; -const pathSearch = 'search'; -const pathSeriesList = 'search/series'; -const pathOperationsList = 'search/operations'; -const pathMetadata = 'meta-data'; - -/** - * Mutualised getter - * @param {*} address : where to fetch data - * @param {*} token : the token - * @returns the json data - */ -const mutualizedGet = async (address, token) => { - const b = await getBaseURI(); - return fetch(`${b}/${address}`, { - headers: getHeaders({ Accept: 'application/json' }, token), - }).then((res) => res.json()); -}; - -/** - * This method adds the OIDC token to the headers of the request - */ -const getHeaders = (base, token) => { - if (!token) return base; - return { - ...base, - Authorization: `Bearer ${token}`, - }; -}; - -/** - * Retrieve all questionnaires - */ -export const getQuestionnaireList = (stamp, token) => - mutualizedGet(`${pathQuestionnaireListSearch}?owner=${stamp}`, token); - -/** - * Create new questionnaire - */ -export const postQuestionnaire = async (qr, token) => { - const b = await getBaseURI(); - return fetch(`${b}/${pathQuestionnaireList}`, { - method: 'POST', - headers: getHeaders({ 'Content-Type': 'application/json' }, token), - body: JSON.stringify(qr), - }).then((res) => { - if (res.ok) return res; - throw new Error(`Network request failed :${res.statusText}`); - }); -}; - -/** - * Update questionnaire by id - */ -export const putQuestionnaire = async (id, qr, token) => { - const b = await getBaseURI(); - return fetch(`${b}/${pathQuestionnaire}/${id}`, { - method: 'PUT', - headers: getHeaders({ 'Content-Type': 'application/json' }, token), - body: JSON.stringify(qr), - }).then((res) => { - if (res.ok) return res; - throw new Error(`Network request failed :${res.statusText}`); - }); -}; - -/** - * Retrieve questionnaire by id - */ -export const getQuestionnaire = (id, token) => - mutualizedGet(`${pathQuestionnaire}/${id}`, token); - -/** - * Retrieve the variables of a given questionnaire - */ -export const getVariablesById = (id, token) => - mutualizedGet(`${pathQuestionnaire}/${id}/variables`, token); - -/** - * Will send a DELETE request in order to remove an existing questionnaire - * - * @param {deleteQuestionnaire} id The id of the questionnaire we want to delete - */ -export const deleteQuestionnaire = async (id, token) => { - const b = await getBaseURI(); - return fetch(`${b}/${pathQuestionnaire}/${id}`, { - method: 'DELETE', - headers: getHeaders({}, token), - }); -}; - -export const getInit = async () => { - const b = await getBaseURI(); - return fetch(`${b}/${pathInit}`, { - headers: getHeaders({ Accept: 'application/json' }), - }).then((res) => res.json()); -}; - -export const getSeries = (token) => mutualizedGet(`${pathSeriesList}`, token); - -export const getOperations = (id, token) => - mutualizedGet(`${pathSeriesList}/${id}/operations`, token); - -export const getCampaigns = (id, token) => - mutualizedGet(`${pathOperationsList}/${id}/collections`, token); - -export const getContextFromCampaign = (id, token) => - mutualizedGet(`${pathSearch}/context/collection/${id}`, token); - -export const getUnitsList = (token) => - mutualizedGet(`${pathMetadata}/units`, token); - -export const getStampsList = async (token) => - mutualizedGet(`persistence/questionnaires/stamps`, token); - -export const getSearchResults = async ( - token, - typeItem, - criterias, - filter = '', -) => { - const b = await getBaseURI(); - return fetch(`${b}/${pathSearch}${getUrlFromCriterias(criterias)}`, { - method: 'POST', - headers: getHeaders({ 'Content-Type': 'application/json' }, token), - body: JSON.stringify({ - types: [typeItem], - filter, - }), - }).then((res) => res.json()); -}; - -export const getNomenclatures = async () => { - return getNomenclaturesContent; -}; - -export const getNomenclature = async (id) => { - return getNomenclaturesContent.nomenclatures[id]; -}; diff --git a/src/api/search.ts b/src/api/search.ts new file mode 100644 index 000000000..00f8824aa --- /dev/null +++ b/src/api/search.ts @@ -0,0 +1,37 @@ +import { getUrlFromCriteria } from '../utils/utils'; +import { computeAuthorizationHeader, getBaseURI } from './utils'; + +const pathSearch = 'search'; + +export async function getContextFromCampaign( + id: string, + token: string, +): Promise { + const url = `${getBaseURI()}/${pathSearch}/context/collection/${id}`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { headers }).then((res) => res.json()); +} + +export const getSearchResults = async ( + token: string, + typeItem: unknown, + criteria: { [key: string]: string | undefined }, + filter: string = '', +): Promise => { + const url = `${getBaseURI()}/${pathSearch}?${getUrlFromCriteria(criteria)}`; + const headers = new Headers(); + headers.append('Content-Type', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { + method: 'POST', + headers, + body: JSON.stringify({ + types: [typeItem], + filter, + }), + }).then((res) => res.json()); +}; diff --git a/src/api/utils.ts b/src/api/utils.ts index 097b02469..dee4493a0 100644 --- a/src/api/utils.ts +++ b/src/api/utils.ts @@ -3,3 +3,7 @@ import { getEnvVar } from '@/utils/env'; export function getBaseURI(): string { return getEnvVar('API_URL') ?? ''; } + +export function computeAuthorizationHeader(token: string): string { + return token ? `Bearer ${token}` : ''; +} diff --git a/src/api/versions.ts b/src/api/versions.ts index 718e55057..ea883bca0 100644 --- a/src/api/versions.ts +++ b/src/api/versions.ts @@ -1,20 +1,20 @@ import type { Version, VersionWithData } from '@/models/versions'; -import { getBaseURI } from './utils'; +import { computeAuthorizationHeader, getBaseURI } from './utils'; /** Fetch latest versions of the questionnaire. */ export const getVersions = async ( id: string, token: string, ): Promise => { - const b = getBaseURI(); - return fetch(`${b}/persistence/questionnaire/${id}/versions`, { - method: 'GET', - headers: { - Accept: 'application/json', - Authorization: token ? `Bearer ${token}` : '', - }, - }).then((res) => res.json() as Promise); + const url = `${getBaseURI()}/persistence/questionnaire/${id}/versions`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { headers }).then( + (res) => res.json() as Promise, + ); }; /** Get details of the specified version to load it. */ @@ -22,12 +22,12 @@ export const getVersion = async ( id: string, token: string, ): Promise => { - const b = getBaseURI(); - return fetch(`${b}/persistence/version/${id}?withData=true`, { - method: 'GET', - headers: { - Accept: 'application/json', - Authorization: token ? `Bearer ${token}` : '', - }, - }).then((res) => res.json() as Promise); + const url = `${getBaseURI()}/persistence/version/${id}?withData=true`; + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + + return fetch(url, { headers }).then( + (res) => res.json() as Promise, + ); }; diff --git a/src/api/visualize.ts b/src/api/visualize.ts index c73452e40..9e931269e 100644 --- a/src/api/visualize.ts +++ b/src/api/visualize.ts @@ -1,6 +1,4 @@ -import { getBaseURI } from './utils'; - -const pathVisualisation = 'transform/visualize'; +import { computeAuthorizationHeader, getBaseURI } from './utils'; type QuestionnaireData = { id: string; @@ -135,14 +133,15 @@ const postVisualization = async ( /** Cntext of the visualization (houseold or business). */ context: VisualizationContext | undefined = undefined, ) => { - const b = getBaseURI(); - const url = buildUrl(b, `${pathVisualisation}${path}`, ref, context); + const contextParam = context ? `&context=${context}` : ''; + const url = `${getBaseURI()}/transform/visualize${path}?references=${ref}${contextParam}`; + const headers = new Headers(); + headers.append('Content-Type', 'application/json'); + headers.append('Authorization', computeAuthorizationHeader(token)); + return fetch(url, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: token ? `Bearer ${token}` : '', - }, + headers, body: JSON.stringify(qr), }).then(async (response) => { if (response.ok) { @@ -156,26 +155,6 @@ const postVisualization = async ( }); }; -/** - * Construct an URL with the given base URL, path, reference, and optional context. - */ -function buildUrl( - /** Backend baseUrl. */ - baseUrl: string, - /** Specific path of the endpoint. */ - path: string, - /** Indicates if the questionnaire contains a reference to another questionnaire. */ - ref: boolean, - /** Optional context of the visualization (HOUSEHOLD or BUSINESS). */ - context: VisualizationContext | undefined = undefined, -): string { - let url = `${baseUrl}/${path}?references=${ref}`; - if (context !== undefined) { - url += `&context=${context}`; - } - return url; -} - /** * This method will emulate the download of file, received from a POST request. * We will dynamically create a A element linked to the downloaded content, and diff --git a/src/index.jsx b/src/index.jsx index 65c7b03d8..40f692413 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -4,7 +4,7 @@ import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import { getInit } from './api/remote-api'; +import { getInit } from './api/init'; import './index.css'; import Router from './router'; import configureStore from './store/configure-store'; diff --git a/src/layout/questionnaire-list/components/questionnaire-list.jsx b/src/layout/questionnaire-list/components/questionnaire-list.jsx index ebf3826c0..38dae240a 100644 --- a/src/layout/questionnaire-list/components/questionnaire-list.jsx +++ b/src/layout/questionnaire-list/components/questionnaire-list.jsx @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import ReactModal from 'react-modal'; -import { getStampsList } from '../../../api/remote-api'; +import { getStampsList } from '../../../api/questionnaires'; import { COMPONENT_TYPE, TCM } from '../../../constants/pogues-constants'; import { formatDate, getState } from '../../../utils/component/component-utils'; import { getWeight } from '../../../utils/component/generic-input-utils'; diff --git a/src/reducers/auth-type.jsx b/src/reducers/auth-type.jsx deleted file mode 100644 index 23eaf57b0..000000000 --- a/src/reducers/auth-type.jsx +++ /dev/null @@ -1,5 +0,0 @@ -import { getEnvVar } from '../utils/env'; - -const auth = getEnvVar('AUTH_TYPE'); - -export const authType = () => auth; diff --git a/src/reducers/index.jsx b/src/reducers/index.jsx index e2d859569..58ff465e7 100644 --- a/src/reducers/index.jsx +++ b/src/reducers/index.jsx @@ -4,7 +4,6 @@ import { reducer as form } from 'redux-form'; import checkers from '../utils/integrity/checkers'; import integrityChecker from '../utils/integrity/integrity-checker'; import appState from './app-state/app-state'; -import { authType } from './auth-type'; import calculatedVariableByQuestionnaire from './calculated-variable-by-questionnaire'; import codeListByQuestionnaire from './code-list-by-questionnaire'; import collectedVariableByQuestionnaire from './collected-variable-by-questionnaire'; @@ -20,7 +19,6 @@ import searchResultById from './search-result-by-id'; export default integrityChecker( combineReducers({ - authType, isSearchDisable: (s = '') => s, form, locale, diff --git a/src/utils/utils.spec.ts b/src/utils/utils.spec.ts index 30dd9b20d..25c7b1e32 100644 --- a/src/utils/utils.spec.ts +++ b/src/utils/utils.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from 'vitest'; import { getSupWeight, - getUrlFromCriterias, + getUrlFromCriteria, nameFromLabel, nestedStoreToFlat, storeToArray, @@ -13,15 +13,15 @@ describe('Utils', () => { test.each([ [undefined, ''], [{}, ''], - [{ key1: 'value-key1' }, '?key1=value-key1'], + [{ key1: 'value-key1' }, 'key1=value-key1'], [{ key1: undefined }, ''], [ { key1: 'value-key1', key2: 'value-key2' }, - '?key1=value-key1&key2=value-key2', + 'key1=value-key1&key2=value-key2', ], - [{ key1: 'value-key1', key2: undefined }, '?key1=value-key1'], - ])('getUrlFromCriterias (%s) -> %s', (criteria, expected) => { - expect(getUrlFromCriterias(criteria)).toBe(expected); + [{ key1: 'value-key1', key2: undefined }, 'key1=value-key1'], + ])('getUrlFromCriteria (%s) -> %s', (criteria, expected) => { + expect(getUrlFromCriteria(criteria)).toBe(expected); }); test.each([ diff --git a/src/utils/utils.ts b/src/utils/utils.ts index d6112fab2..25e5295c2 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,5 @@ /** Compute search parameters based on provided object. */ -export function getUrlFromCriterias( +export function getUrlFromCriteria( criteria: { [key: string]: string | undefined } = {}, ): string { let url = ''; @@ -7,9 +7,7 @@ export function getUrlFromCriterias( for (const key in criteria) { const v = criteria[key]; if (v) { - if (url === '') { - url += '?'; - } else { + if (url !== '') { url += '&'; } url += `${key}=${encodeURIComponent(v)}`;