diff --git a/package.json b/package.json index 7a11bd6..052a87b 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "sonor", - "version": "0.5.33", + "version": "0.5.35", "private": true, "dependencies": { + "@tanstack/react-query": "4.0.5", "@testing-library/jest-dom": "^4.2.4", "async-wait-until": "^2.0.5", "awesome-debounce-promise": "^2.1.0", @@ -12,8 +13,8 @@ "cypress-multi-reporters": "^1.2.4", "font-awesome": "^4.7.0", "history": "^5.0.0", - "keycloak-js": "4.0.0-beta.2", "mocha-junit-reporter": "^1.23.3", + "oidc-spa": "4.5.1", "prop-types": "^15.7.2", "react": "^16.13.1", "react-bootstrap": "^1.0.1", diff --git a/public/configuration.json b/public/configuration.json index 2274d68..916a5f6 100644 --- a/public/configuration.json +++ b/public/configuration.json @@ -2,8 +2,9 @@ "PEARL_JAM_URL": "http://localhost:7777", "QUEEN_URL_BACK_END": "http://localhost:7777", "QUEEN_URL_FRONT_END": "http://localhost:7777", - + "ISSUER_URI": "http://localhost:7777", + "OIDC_CLIENT_ID": "clientId", "AUTHENTICATION_MODE": "anonymous", - "_AUTHENTICATION_MODE_COMMENT_": "Use 'keycloak' or 'anonymous'" + "_AUTHENTICATION_MODE_COMMENT_": "Use 'oidc' or 'anonymous'" } diff --git a/public/silent-sso.html b/public/silent-sso.html new file mode 100644 index 0000000..d5759a1 --- /dev/null +++ b/public/silent-sso.html @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/src/Authentication/useAuth.js b/src/Authentication/useAuth.js new file mode 100644 index 0000000..2dd0975 --- /dev/null +++ b/src/Authentication/useAuth.js @@ -0,0 +1,51 @@ +import { useEffect } from "react"; +import { createReactOidc } from "oidc-spa/react"; + +/** + * By default, without initialization we use a mock as a return of "useOidc" + * + * This object will be used for testing purpose to simulate authentication status + */ +const mockOidc = { login: () => {}, isUserLoggedIn: false, oidcTokens: {} } +// Global method that will be replaced when oidc is initialized +let useOidc = ({assertUserLoggedIn}) => mockOidc; + +/** + * Helper method used for tests, set a fake Oidc authentication state + */ +export const mockOidcForUser = () => { + window.localStorage.setItem("AUTHENTICATION_MODE", "oidc") + mockOidc.isUserLoggedIn = true + mockOidc.oidcTokens = {accessToken: '12031203'} +} +export const mockOidcFailed = () => { + mockOidc.isUserLoggedIn = false + mockOidc.oidcTokens = {} +} + +/** + * Initialize oidc + */ +export function initializeOidc (config) { + const oidc = createReactOidc(config) + useOidc = oidc.useOidc + return oidc; +} + +/** + * Retrieve authentication status based of Oidc + */ +export function useIsAuthenticated() { + const { login, isUserLoggedIn, oidcTokens, renewTokens} = useOidc({ assertUserLoggedIn: false }); + + useEffect(() => { + if (!login) { + return; + } + login({ + doesCurrentHrefRequiresAuth: true, + }); + }, [login]); + + return { isAuthenticated: isUserLoggedIn, tokens: oidcTokens, renewTokens }; +} diff --git a/src/components/App/App.jsx b/src/components/App/App.jsx index 6ee0550..48659a2 100644 --- a/src/components/App/App.jsx +++ b/src/components/App/App.jsx @@ -1,87 +1,88 @@ -import React from 'react'; -import Keycloak from 'keycloak-js'; -import View from '../View/View'; -import DataFormatter from '../../utils/DataFormatter'; -import { KEYCLOAK, ANONYMOUS } from '../../utils/constants.json'; -import initConfiguration from '../../initConfiguration'; -import D from '../../i18n'; +import React, { useEffect, useState, useRef } from "react"; +import { useIsAuthenticated } from "../../Authentication/useAuth"; +import D from "../../i18n"; +import View from "../View/View"; +import DataFormatter from "../../utils/DataFormatter"; +import { OIDC, ANONYMOUS } from "../../utils/constants.json"; -class App extends React.Component { - constructor(props) { - super(props); - this.state = { - keycloak: null, - authenticated: false, - contactFailed: false, - initialisationFailed: false, - data: null, +export const App = () => { + const [authenticated, setAuthenticated] = useState(false); + const [contactFailed, setContactFailed] = useState(false); + const [data, setData] = useState(null); + const timeoutIdRef = useRef(null); + + const { tokens, renewTokens } = useIsAuthenticated(); + + useEffect(() => { + const resetInactivityTimeout = () => { + if (timeoutIdRef.current) { + clearTimeout(timeoutIdRef.current); + } + timeoutIdRef.current = setTimeout(renewTokens, 5 * 60 * 1000); }; - } - async componentDidMount() { - try { - await initConfiguration(); - } catch (e) { - this.setState({ initialisationFailed: true }); - } - if (window.localStorage.getItem('AUTHENTICATION_MODE') === ANONYMOUS) { + const events = [ + "mousemove", + "mousedown", + "keypress", + "touchstart", + "click", + ]; + + events.forEach((event) => { + window.addEventListener(event, resetInactivityTimeout); + }); + + resetInactivityTimeout(); + + return () => { + if (timeoutIdRef.current) { + clearTimeout(timeoutIdRef.current); + } + events.forEach((event) => { + window.removeEventListener(event, resetInactivityTimeout); + }); + }; + }, [renewTokens]); + + useEffect(() => { + if (window.localStorage.getItem("AUTHENTICATION_MODE") === ANONYMOUS) { const dataRetreiver = new DataFormatter(); dataRetreiver.getUserInfo((data) => { if (data.error) { - this.setState({ contactFailed: true }); + setContactFailed(true); } else { - this.setState({ authenticated: true, data }); + setAuthenticated(true); + setData(data); } }); - } else if (window.localStorage.getItem('AUTHENTICATION_MODE') === KEYCLOAK) { - const keycloak = Keycloak('/keycloak.json'); - keycloak.init({ onLoad: 'login-required', checkLoginIframe: false }).then((authenticated) => { - const dataRetreiver = new DataFormatter(keycloak); - dataRetreiver.getUserInfo((data) => { - this.setState({ keycloak, authenticated, data }); - }); - // Update 20 seconds before expiracy - const updateInterval = (keycloak.tokenParsed.exp + keycloak.timeSkew) - * 1000 - - new Date().getTime() - - 20000; - setInterval(() => { - keycloak.updateToken(100).error(() => { - throw new Error('Failed to refresh token'); - }); - }, updateInterval); + } else if ( + window.localStorage.getItem("AUTHENTICATION_MODE") === OIDC && + tokens?.accessToken + ) { + const dataRetreiver = new DataFormatter(tokens.accessToken); + dataRetreiver.getUserInfo((data) => { + setAuthenticated(data !== undefined); + setData(data); }); } - } + }, [tokens]); - render() { - const { - keycloak, authenticated, data, contactFailed, initialisationFailed, - } = this.state; - if (keycloak || authenticated) { - if (authenticated) { - return ( -
+ if (!tokens?.accessToken) { + return
{D.initializationFailed}
; + } - -
- ); - } - return (
{D.unableToAuthenticate}
); - } - if (initialisationFailed) { - return (
{D.initializationFailed}
); - } - if (contactFailed) { - return (
{D.cannotContactServer}
); - } + if (authenticated && tokens?.accessToken && data) { return ( -
{D.initializing}
+
+ +
); } -} -export default App; + if (contactFailed) { + return
{D.cannotContactServer}
; + } + + return
{D.initializing}
; +}; diff --git a/src/components/App/App.test.jsx b/src/components/App/App.test.jsx index 1ada40c..93a2c5a 100644 --- a/src/components/App/App.test.jsx +++ b/src/components/App/App.test.jsx @@ -1,14 +1,12 @@ // Link.react.test.js import React from 'react'; -import { - render, screen, cleanup, waitForElement, -} from '@testing-library/react'; -import Keycloak from 'keycloak-js'; +import { render, screen, cleanup } from "@testing-library/react"; import { NotificationManager } from 'react-notifications'; import DataFormatter from '../../utils/DataFormatter'; -import App from './App'; +import { App } from "./App"; import mocks from '../../tests/mocks'; import C from '../../utils/constants.json'; +import { mockOidcForUser, mockOidcFailed } from "../../Authentication/useAuth"; jest.mock( "../../../package.json", @@ -29,7 +27,6 @@ Date.now = jest.fn(() => 1597916474000); afterEach(cleanup); -jest.mock('keycloak-js'); jest.mock('react-notifications'); jest.mock('../../utils/DataFormatter'); jest.mock('../../initConfiguration'); @@ -53,15 +50,6 @@ const mockError = jest.fn(); NotificationManager.success = mockSuccess; NotificationManager.error = mockError; -Keycloak.init = jest.fn(() => (Promise.resolve({ token: 'abc' }))); - -Keycloak.mockImplementation(() => ({ - init: jest.fn(() => (Promise.resolve({ token: 'abc' }))), - updateToken: (() => ({ error: (() => {}) })), - tokenParsed: { exp: 300 }, - timeSkew: 0, -})); - const updatePreferences = jest.fn((newPrefs, cb) => { if (newPrefs.includes('simpsonkgs2020x00')) { cb({ status: 500 }); @@ -104,52 +92,15 @@ DataFormatter.mockImplementation(() => ({ updatePreferences, })); -it('Component is displayed (initializing)', async () => { - const component = render( - , - ); - // Should match snapshot - expect(component).toMatchSnapshot(); -}); - -it('Component is displayed (anonymous mode)', async () => { - Object.getPrototypeOf(window.localStorage).getItem = jest.fn(() => ('anonymous')); - const component = render( - , - ); - - await waitForElement(() => screen.getByTestId('pagination-nav')); - - // Should match snapshot +it("Component is displayed ", async () => { + mockOidcForUser(); + const component = render(); + await screen.findByText("List of surveys"); expect(component).toMatchSnapshot(); }); -it('Component is displayed (keycloak mode)', async () => { - Object.getPrototypeOf(window.localStorage).getItem = jest.fn(() => ('keycloak')); - const component = render( - , - ); - - await waitForElement(() => screen.getByTestId('pagination-nav')); - - // Should match snapshot - expect(component).toMatchSnapshot(); -}); - -it('Could not authenticate with keycloak', async () => { - Object.getPrototypeOf(window.localStorage).getItem = jest.fn(() => ('keycloak')); - - Keycloak.mockImplementation(() => ({ - init: jest.fn(() => (Promise.resolve(false))), - updateToken: (() => ({ error: (() => {}) })), - tokenParsed: { exp: 300 }, - timeSkew: 0, - })); - - const component = render( - , - ); - - // Should match snapshot +it("Component is not displayed ", async () => { + mockOidcFailed(); + const component = render(); expect(component).toMatchSnapshot(); }); diff --git a/src/components/App/__snapshots__/App.test.jsx.snap b/src/components/App/__snapshots__/App.test.jsx.snap index beafead..6d307da 100644 --- a/src/components/App/__snapshots__/App.test.jsx.snap +++ b/src/components/App/__snapshots__/App.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Component is displayed (anonymous mode) 1`] = ` +exports[`Component is displayed 1`] = ` Object { "asFragment": [Function], "baseElement": @@ -2309,2397 +2309,19 @@ Object { } `; -exports[`Component is displayed (initializing) 1`] = ` +exports[`Component is not displayed 1`] = ` Object { "asFragment": [Function], "baseElement":
- Initializing... + Could not load settings
, "container":
- Initializing... -
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`Component is displayed (keycloak mode) 1`] = ` -Object { - "asFragment": [Function], - "baseElement": -
-
-
-
-
-
- -
-
- - - - - - - - -
-
-
-
- - -
-
-
-
-
- Bienvenue - Chloé -   - Dupont -
-
- 8/20/2020 -
-
-
-
- -
-
- -
-
-
-
-
-
-
-
-
-
-
- List of surveys -
- - - Display - -
-
- -
-
- - elements - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - Survey Units -
- - - - - Survey - - - - - - - Collection start date - - - - - - Collection end date - - - - - - Processing end date - - - - - - - Phase - - - - - - - Allocated - - - - - - To treat by interviewer - - - - - - To be assigned - - - - - - To follow up - - - - - - To be reviewed - - - - - - Finalized -
-
-
-
    -
  • - - 1 - - (current) - - -
  • -
-
-
-
-
-
-
- , - "container":
-
-
-
-
-
- -
-
- - - - - - - - -
-
-
-
- - -
-
-
-
-
- Bienvenue - Chloé -   - Dupont -
-
- 8/20/2020 -
-
-
-
- -
-
- -
-
-
-
-
-
-
-
-
-
-
- List of surveys -
- - - Display - -
-
- -
-
- - elements - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - Survey Units -
- - - - - Survey - - - - - - - Collection start date - - - - - - Collection end date - - - - - - Processing end date - - - - - - - Phase - - - - - - - Allocated - - - - - - To treat by interviewer - - - - - - To be assigned - - - - - - To follow up - - - - - - To be reviewed - - - - - - Finalized -
-
-
-
    -
  • - - 1 - - (current) - - -
  • -
-
-
-
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`Could not authenticate with keycloak 1`] = ` -Object { - "asFragment": [Function], - "baseElement": -
-
- Initializing... -
-
- , - "container":
-
- Initializing... + Could not load settings
, "debug": [Function], diff --git a/src/components/CollectionTable/CollectionTableDisplay.jsx b/src/components/CollectionTable/CollectionTableDisplay.jsx index 98906da..62fdcde 100644 --- a/src/components/CollectionTable/CollectionTableDisplay.jsx +++ b/src/components/CollectionTable/CollectionTableDisplay.jsx @@ -272,12 +272,10 @@ function CollectionTableDisplay({ (pagination.page - 1) * pagination.size, Math.min(pagination.page * pagination.size, displayedLines.length), ) - .map((line) => ( - - ))} + .map((line) => ( + + ) + )} {tableFooter} diff --git a/src/components/MonitoringTable/FollowUpTable.jsx b/src/components/MonitoringTable/FollowUpTable.jsx index 7f7ce5c..718b1c5 100644 --- a/src/components/MonitoringTable/FollowUpTable.jsx +++ b/src/components/MonitoringTable/FollowUpTable.jsx @@ -187,7 +187,7 @@ function FollowUpTable({ Math.min(pagination.page * pagination.size, displayedLines.length), ) .map((line) => ( - + ))} {tableFooter} diff --git a/src/components/View/View.jsx b/src/components/View/View.jsx index 13b6313..26e9a4b 100644 --- a/src/components/View/View.jsx +++ b/src/components/View/View.jsx @@ -29,7 +29,7 @@ class View extends React.Component { loadingPreferences: true, campaigns: [] }; - this.dataRetreiver = new DataFormatter(props.keycloak); + this.dataRetreiver = new DataFormatter(props.token); } componentDidMount() { diff --git a/src/index.js b/src/index.js index a4534dc..8aa158c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,57 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; -import App from './components/App/App'; import 'bootstrap/dist/css/bootstrap.min.css'; import 'font-awesome/css/font-awesome.min.css'; import 'react-notifications/lib/notifications.css'; import 'whatwg-fetch'; +import { initializeOidc } from './Authentication/useAuth'; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { App } from "./components/App/App" + +async function main() { + // Load OIDC configuration + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + const response = await fetch(`${publicUrl.origin}/configuration.json`) + const configuration = await response.json(); + + // Inject configuration infos in the localStorage + window.localStorage.setItem( + 'AUTHENTICATION_MODE', + configuration.AUTHENTICATION_MODE, + ); + window.localStorage.setItem('PEARL_JAM_URL', configuration.PEARL_JAM_URL); + window.localStorage.setItem('QUEEN_URL_BACK_END', configuration.QUEEN_URL_BACK_END); + window.localStorage.setItem('QUEEN_URL_FRONT_END', configuration.QUEEN_URL_FRONT_END); + + // Initialize OIDC globally to use it later + const {OidcProvider} = initializeOidc({ + issuerUri: configuration.ISSUER_URI, + clientId: configuration.OIDC_CLIENT_ID, + publicUrl: "/", + }) + + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 10000, + refetchOnWindowFocus: false, + retry: false, + }, + }, + }); + + ReactDOM.render( + + + + + + + , + document.getElementById('root'), + ); +} + +main(); -ReactDOM.render( - - - , - document.getElementById('root'), -); diff --git a/src/tests/mocks/formattedDataMonitoringTableBysurvey.js b/src/tests/mocks/formattedDataMonitoringTableBysurvey.js index a9afe72..445123c 100644 --- a/src/tests/mocks/formattedDataMonitoringTableBysurvey.js +++ b/src/tests/mocks/formattedDataMonitoringTableBysurvey.js @@ -3,6 +3,7 @@ const val = { "linesDetails": [ { "completionRate": 0.057692307692307696, + "campaignId": "simpsons2020x00", "total": 82, "notStarted": 22, "onGoing": 76, @@ -17,6 +18,7 @@ const val = { }, { "completionRate": 0.057692307692307696, + "campaignId": "simpsosfqns2020x00", "total": 82, "notStarted": 22, "onGoing": 76, @@ -31,6 +33,7 @@ const val = { }, { "completionRate": 0.057692307692307696, + "campaignId": "vqs2fsqe021x00", "total": 82, "notStarted": 22, "onGoing": 76, @@ -45,6 +48,7 @@ const val = { }, { "completionRate": 0.057692307692307696, + "campaignId": "simpsonqsdfsqes2020x00", "total": 82, "notStarted": 22, "onGoing": 76, @@ -59,6 +63,7 @@ const val = { }, { "completionRate": 0.057692307692307696, + "campaignId": "vqs2qfsdfsqe021x00", "total": 82, "notStarted": 22, "onGoing": 76, @@ -73,6 +78,7 @@ const val = { }, { "completionRate": 0.057692307692307696, + "campaignId": "simpsonkgs2020x00", "total": 82, "notStarted": 22, "onGoing": 76, @@ -87,6 +93,7 @@ const val = { }, { "completionRate": 0.057692307692307696, + "campaignId": "vqs202fgd1x00", "total": 82, "notStarted": 22, "onGoing": 76, diff --git a/src/utils/DataFormatter.js b/src/utils/DataFormatter.js index 6ddcaa8..466278a 100644 --- a/src/utils/DataFormatter.js +++ b/src/utils/DataFormatter.js @@ -10,8 +10,8 @@ import Service from "./Service"; import Utils from "./Utils"; class DataFormatter { - constructor(keycloak) { - this.service = new Service(keycloak); + constructor(token) { + this.service = new Service(token); } getInterviewers(cb) { @@ -491,6 +491,7 @@ class DataFormatter { res.map((x) => { const obj = Utils.formatForMonitoringTable(x); obj.survey = x.campaign.label; + obj.campaignId = x.campaign.id; return obj; }) ); @@ -674,7 +675,7 @@ class DataFormatter { .filter((camp) => camp.total) .map((camp) => Utils.formatForCollectionTable( - { survey: camp.campaign.label }, + { survey: camp.campaign.label, campaignId: camp.campaign?.id }, data[0].find((c) => c.campaign.id === camp.campaign.id), camp ) diff --git a/src/utils/Service.js b/src/utils/Service.js index 84347a2..45bedf8 100644 --- a/src/utils/Service.js +++ b/src/utils/Service.js @@ -1,20 +1,18 @@ import { NotificationManager } from "react-notifications"; import D from "../i18n"; - -const baseUrlPearlJam = `${window.localStorage.getItem("PEARL_JAM_URL")}`; -const baseUrlQueen = `${window.localStorage.getItem("QUEEN_URL_BACK_END")}`; - class Service { - constructor(keycloak) { - this.keycloak = keycloak; + constructor(token) { + this.token = token; + this.baseUrlPearlJam = `${window.localStorage.getItem("PEARL_JAM_URL")}`; + this.baseUrlQueen = `${window.localStorage.getItem("QUEEN_URL_BACK_END")}`; } - makeOptions() { - if (this.keycloak) { + if (this.token) { + return { headers: new Headers({ "Content-Type": "application/json", - Authorization: `Bearer ${this.keycloak.token}`, + Authorization: `Bearer ${this.token}`, }), }; } @@ -31,7 +29,7 @@ class Service { getSurveyUnits(campaignId, state, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units?${ + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units?${ state ? `state=${state}` : "" }`, this.makeOptions() @@ -55,7 +53,7 @@ class Service { } async getSurveyUnitsQuestionnaireId(listSurveyUnitIds, cb) { - return fetch(`${baseUrlQueen}/api/survey-units/questionnaire-model-id`, { + return fetch(`${this.baseUrlQueen}/api/survey-units/questionnaire-model-id`, { ...this.makeOptions(), method: "POST", body: JSON.stringify(listSurveyUnitIds), @@ -76,7 +74,7 @@ class Service { } getSurveyUnitsClosable(cb) { - fetch(`${baseUrlPearlJam}/api/survey-units/closable`, this.makeOptions()) + fetch(`${this.baseUrlPearlJam}/api/survey-units/closable`, this.makeOptions()) .then((res) => res.json()) .then((data) => { cb(data); @@ -93,7 +91,7 @@ class Service { getSurveyUnitsNotAttributedByCampaign(campaignId, cb) { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/not-attributed`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/not-attributed`, this.makeOptions() ) .then((res) => res.json()) @@ -108,7 +106,7 @@ class Service { getSurveyUnitsAbandonedByCampaign(campaignId, cb) { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/abandoned`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/abandoned`, this.makeOptions() ) .then((res) => res.json()) @@ -122,7 +120,7 @@ class Service { } getStatesBySurveyUnit(su, cb) { - fetch(`${baseUrlPearlJam}/api/survey-unit/${su}/states`, this.makeOptions()) + fetch(`${this.baseUrlPearlJam}/api/survey-unit/${su}/states`, this.makeOptions()) .then((res) => res.json()) .then((data) => { cb(data); @@ -141,7 +139,7 @@ class Service { const options = {}; Object.assign(options, this.makeOptions()); options.method = "PUT"; - fetch(`${baseUrlPearlJam}/api/survey-unit/${su}/state/FIN`, options) + fetch(`${this.baseUrlPearlJam}/api/survey-unit/${su}/state/FIN`, options) .then((res) => cb(res)) .catch((e) => { console.error(e); @@ -153,7 +151,7 @@ class Service { const options = {}; Object.assign(options, this.makeOptions()); options.method = "PUT"; - fetch(`${baseUrlPearlJam}/api/survey-unit/${su}/state/${state}`, options) + fetch(`${this.baseUrlPearlJam}/api/survey-unit/${su}/state/${state}`, options) .then((res) => cb(res)) .catch((e) => { console.error(e); @@ -166,7 +164,7 @@ class Service { Object.assign(options, this.makeOptions()); options.method = "PUT"; fetch( - `${baseUrlPearlJam}/api/survey-unit/${su}/close/${closingCause}`, + `${this.baseUrlPearlJam}/api/survey-unit/${su}/close/${closingCause}`, options ) .then((res) => cb(res)) @@ -181,7 +179,7 @@ class Service { Object.assign(options, this.makeOptions()); options.method = "PUT"; fetch( - `${baseUrlPearlJam}/api/survey-unit/${su}/closing-cause/${closingCause}`, + `${this.baseUrlPearlJam}/api/survey-unit/${su}/closing-cause/${closingCause}`, options ) .then((res) => cb(res)) @@ -196,7 +194,7 @@ class Service { Object.assign(options, this.makeOptions()); options.method = "PUT"; options.body = JSON.stringify(comment); - fetch(`${baseUrlPearlJam}/api/survey-unit/${su}/comment`, options) + fetch(`${this.baseUrlPearlJam}/api/survey-unit/${su}/comment`, options) .then((res) => cb(res)) .catch((e) => { console.error(e); @@ -208,7 +206,7 @@ class Service { const options = {}; Object.assign(options, this.makeOptions()); options.method = "PUT"; - fetch(`${baseUrlPearlJam}/api/survey-unit/${su}/viewed`, options) + fetch(`${this.baseUrlPearlJam}/api/survey-unit/${su}/viewed`, options) .then((res) => cb(res)) .catch((e) => { console.error(e); @@ -227,7 +225,7 @@ class Service { Object.assign(options, this.makeOptions()); options.method = "PUT"; options.body = JSON.stringify(preferences); - fetch(`${baseUrlPearlJam}/api/preferences`, options) + fetch(`${this.baseUrlPearlJam}/api/preferences`, options) .then((res) => cb(res)) .catch(console.error); } @@ -240,7 +238,7 @@ class Service { // -------------------- // getUser(cb) { return new Promise((resolve) => { - fetch(`${baseUrlPearlJam}/api/user`, this.makeOptions()) + fetch(`${this.baseUrlPearlJam}/api/user`, this.makeOptions()) .then((res) => res.json()) .then((data) => { if (cb) { @@ -269,7 +267,7 @@ class Service { // Campaigns service begin // // ----------------------- // getCampaigns(cb) { - fetch(`${baseUrlPearlJam}/api/campaigns`, this.makeOptions()) + fetch(`${this.baseUrlPearlJam}/api/campaigns`, this.makeOptions()) .then((res) => res.json()) .then((data) => { cb(data); @@ -287,7 +285,7 @@ class Service { getCampaignsByInterviewer(idep, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/interviewer/${idep}/campaigns`, + `${this.baseUrlPearlJam}/api/interviewer/${idep}/campaigns`, this.makeOptions() ) .then((res) => res.json()) @@ -316,7 +314,7 @@ class Service { getStateCount(campaignId, date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/state-count?date=${date}`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/state-count?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -335,7 +333,7 @@ class Service { getStateCountNotAttributed(campaignId, date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/not-attributed/state-count?date=${date}`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/not-attributed/state-count?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -358,7 +356,7 @@ class Service { getStateCountByInterviewer(campaignId, idep, date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/interviewer/${idep}/state-count?date=${date}`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/interviewer/${idep}/state-count?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -381,7 +379,7 @@ class Service { getStateCountByCampaign(date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaigns/survey-units/state-count?date=${date}`, + `${this.baseUrlPearlJam}/api/campaigns/survey-units/state-count?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -399,7 +397,7 @@ class Service { getStateCountTotalByCampaign(campaignId, cb) { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/state-count`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/state-count`, this.makeOptions() ) .then((res) => res.json()) @@ -425,7 +423,7 @@ class Service { getContactOutcomes(campaignId, date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/contact-outcomes?date=${date}`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/contact-outcomes?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -444,7 +442,7 @@ class Service { getContactOutcomesNotAttributed(campaignId, date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/not-attributed/contact-outcomes?date=${date}`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/not-attributed/contact-outcomes?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -467,7 +465,7 @@ class Service { getContactOutcomesByInterviewer(campaignId, idep, date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/interviewer/${idep}/contact-outcomes?date=${date}`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/interviewer/${idep}/contact-outcomes?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -490,7 +488,7 @@ class Service { getContactOutcomesByCampaign(date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaigns/survey-units/contact-outcomes?date=${date}`, + `${this.baseUrlPearlJam}/api/campaigns/survey-units/contact-outcomes?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -515,7 +513,7 @@ class Service { getClosingCausesByInterviewer(campaignId, idep, date, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/interviewer/${idep}/closing-causes?date=${date}`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/survey-units/interviewer/${idep}/closing-causes?date=${date}`, this.makeOptions() ) .then((res) => res.json()) @@ -542,7 +540,7 @@ class Service { // Interviewers service begin // // --------------------------- // getInterviewers(cb) { - fetch(`${baseUrlPearlJam}/api/interviewers`, this.makeOptions()) + fetch(`${this.baseUrlPearlJam}/api/interviewers`, this.makeOptions()) .then((res) => res.json()) .then((data) => { cb(data); @@ -556,7 +554,7 @@ class Service { getInterviewersByCampaign(campaignId, cb) { return new Promise((resolve) => { fetch( - `${baseUrlPearlJam}/api/campaign/${campaignId}/interviewers`, + `${this.baseUrlPearlJam}/api/campaign/${campaignId}/interviewers`, this.makeOptions() ) .then((res) => res.json()) @@ -578,7 +576,7 @@ class Service { // ----------------------------- // getQuestionnaireId(campaignId, cb) { fetch( - `${baseUrlQueen}/api/campaign/${campaignId}/questionnaire-id`, + `${this.baseUrlQueen}/api/campaign/${campaignId}/questionnaire-id`, this.makeOptions() ) .then((res) => res.json()) @@ -603,7 +601,7 @@ class Service { options.method = "POST"; options.body = JSON.stringify(body); - fetch(`${baseUrlPearlJam}/api/message`, options).then((data) => { + fetch(`${this.baseUrlPearlJam}/api/message`, options).then((data) => { cb(data); }); } @@ -614,7 +612,7 @@ class Service { options.method = "POST"; options.body = JSON.stringify({ text }); - fetch(`${baseUrlPearlJam}/api/verify-name`, options) + fetch(`${this.baseUrlPearlJam}/api/verify-name`, options) .then((res) => res.json()) .then((data) => { cb(data); @@ -626,7 +624,7 @@ class Service { } getMessageHistory(cb) { - fetch(`${baseUrlPearlJam}/api/message-history`, this.makeOptions()) + fetch(`${this.baseUrlPearlJam}/api/message-history`, this.makeOptions()) .then((res) => res.json()) .then((data) => { cb(data); diff --git a/src/utils/Service.test.js b/src/utils/Service.test.js index e52c593..d989b6a 100644 --- a/src/utils/Service.test.js +++ b/src/utils/Service.test.js @@ -26,7 +26,7 @@ const { const service = new Service(); it('Test option creation', async () => { - const s = new Service({ token: 'ABC' }); + const s = new Service('ABC'); // Should return correct options expect(s.makeOptions()).toEqual({ headers: { map: { authorization: 'Bearer ABC', 'content-type': 'application/json' } } }); }); diff --git a/src/utils/Utils.js b/src/utils/Utils.js index 18b6a29..ffaeecf 100644 --- a/src/utils/Utils.js +++ b/src/utils/Utils.js @@ -82,10 +82,6 @@ class Utils { line.appointmentTaken = stateCount.apsCount; line.interviewStarted = stateCount.insCount; - line.interviewerFirstName = stateCount.interviewerFirstName; - line.interviewerFirstName = stateCount.interviewerLastName; - line.interviewerId = stateCount.interviewerId; - return line; } diff --git a/src/utils/constants.json b/src/utils/constants.json index 4940fac..18c5940 100644 --- a/src/utils/constants.json +++ b/src/utils/constants.json @@ -1,11 +1,10 @@ { "KEYCLOAK": "keycloak", "ANONYMOUS": "anonymous", - - "BY_INTERVIEWER_ONE_SURVEY":0, - "BY_INTERVIEWER":1, - "BY_SURVEY":2, + "OIDC": "oidc", + "BY_INTERVIEWER_ONE_SURVEY": 0, + "BY_INTERVIEWER": 1, + "BY_SURVEY": 2, "BY_SITE": 3, "BY_SURVEY_ONE_INTERVIEWER": 4 } - diff --git a/yarn.lock b/yarn.lock index 7437d64..c9c4a2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1828,6 +1828,20 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" +"@tanstack/query-core@^4.0.0-beta.1": + version "4.36.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.36.1.tgz#79f8c1a539d47c83104210be2388813a7af2e524" + integrity sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA== + +"@tanstack/react-query@4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.0.5.tgz#4597ac03394ddfa6ad8b5e1beb6282468623d398" + integrity sha512-tIggVlhoFevVpY/LkZroPmrERFHN8tw4aZLtgwSArzHmMJ03WQcaNvbbHy6GERidXtaMdUz+IeQryrE7cO7WPQ== + dependencies: + "@tanstack/query-core" "^4.0.0-beta.1" + "@types/use-sync-external-store" "^0.0.3" + use-sync-external-store "^1.2.0" + "@testing-library/dom@*": version "9.3.3" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.3.tgz#108c23a5b0ef51121c26ae92eb3179416b0434f5" @@ -2094,6 +2108,11 @@ "@types/testing-library__dom" "*" pretty-format "^25.1.0" +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + "@types/warning@^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798" @@ -4180,6 +4199,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + css-blank-pseudo@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" @@ -8117,10 +8141,10 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: object.assign "^4.1.4" object.values "^1.1.6" -keycloak-js@4.0.0-beta.2: - version "4.0.0-beta.2" - resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-4.0.0-beta.2.tgz#01f791da18db2cecce467cd00d79dd49f847ba7d" - integrity sha512-cMoiOOboVmK4DTXZMeShPfDfwciIc/QNMBfU90XuQfjQKeNmUZZFOzpdH5bYv7In61dCDBAdojcK5+y5sFXOmg== +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== killable@^1.0.1: version "1.0.1" @@ -9195,6 +9219,23 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +oidc-client-ts@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz#764c8a33de542026e2798de9849ce8049047d7e5" + integrity sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w== + dependencies: + crypto-js "^4.2.0" + jwt-decode "^3.1.2" + +oidc-spa@4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/oidc-spa/-/oidc-spa-4.5.1.tgz#5bf772143d739aa9a6907fb61f17ac55e595f14a" + integrity sha512-MMgjT2d3zpdh91B6vehgAMlKD0rUAxQeowx3wFCs64fSiH5zD9mjXsqoflAEdelsbvvHv7h+sTv3/Znbquhj8w== + dependencies: + jwt-decode "^3.1.2" + oidc-client-ts "2.4.0" + tsafe "^1.6.5" + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -12579,6 +12620,11 @@ ts-pnp@^1.1.2: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== +tsafe@^1.6.5: + version "1.6.6" + resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.6.6.tgz#fd93e64d6eb13ef83ed1650669cc24bad4f5df9f" + integrity sha512-gzkapsdbMNwBnTIjgO758GujLCj031IgHK/PKr2mrmkCSJMhSOR5FeOuSxKLMUoYc0vAA4RGEYYbjt/v6afD3g== + tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" @@ -12872,6 +12918,11 @@ url@^0.11.0: punycode "^1.4.1" qs "^6.11.2" +use-sync-external-store@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" + integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"