diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ec78cc5..c0704de1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ * Add `tr` Türkçe (Turkish) locale. Refs STCOR-1026. * Remove the `/users-keycloak/_self` (and `/bl-users/_self`) requests. They were included in `isAuthenticationRequest`, which caused them to bypass the RTR queue (`getPromise`). Fixes STCOR-1029. * Define a permission-set containing values necessary for session-init API requests. Refs STCOR-1031. +* Use GET mod-settings `/locale` API to get tenant language & locale settings. Refs STCOR-1027. ## [11.0.0](https://github.com/folio-org/stripes-core/tree/v11.0.0) (2025-02-24) [Full Changelog](https://github.com/folio-org/stripes-core/compare/v10.2.0...v11.0.0) diff --git a/src/loginServices.js b/src/loginServices.js index 72ce4f463..0ac5579f1 100644 --- a/src/loginServices.js +++ b/src/loginServices.js @@ -327,77 +327,13 @@ export async function loadTranslations(store, locale, defaultTranslations = {}) * @param {string} tenant * @returns {Promise} */ -async function dispatchLocale(url, store, tenant) { - const response = await fetch(url, { - headers: getHeaders(tenant, store.getState().okapi.token), - credentials: 'include', - mode: 'cors', - }); - - if (response.ok) { - const json = await response.json(); - if (json.configs?.length) { - const localeValues = JSON.parse(json.configs[0].value); - const { locale, timezone, currency } = localeValues; - if (locale) { - await loadTranslations(store, locale); - } - if (timezone) store.dispatch(setTimezone(timezone)); - if (currency) store.dispatch(setCurrency(currency)); - } +async function dispatchLocale(localeValues, store) { + const { locale, timezone, currency } = localeValues; + if (locale) { + await loadTranslations(store, locale); } - - return response; -} - -/** - * getLocale - * return a promise that retrieves the tenant's locale-settings then - * loads the translations and dispatches the timezone and currency. - * @param {string} okapiUrl - * @param {redux store} store - * @param {string} tenant - * - * @returns {Promise} - */ -export async function getLocale(okapiUrl, store, tenant) { - const query = [ - 'module==ORG', - 'configName == localeSettings', - '(cql.allRecords=1 NOT userId="" NOT code="")' - ].join(' AND '); - - const res = await dispatchLocale( - `${okapiUrl}/configurations/entries?query=(${query})`, - store, - tenant - ); - - return res; -} - -/** - * getUserLocale - * return a promise that retrieves the user's locale-settings then - * loads the translations and dispatches the timezone and currency. - * @param {*} okapiUrl - * @param {*} store - * @param {*} tenant - * - * @returns {Promise} - */ -export async function getUserLocale(okapiUrl, store, tenant, userId) { - const query = Object.entries(userLocaleConfig) - .map(([k, v]) => `"${k}"=="${v}"`) - .join(' AND '); - - const res = await dispatchLocale( - `${okapiUrl}/configurations/entries?query=(${query} and userId=="${userId}")`, - store, - tenant - ); - - return res; + if (timezone) store.dispatch(setTimezone(timezone)); + if (currency) store.dispatch(setCurrency(currency)); } /** @@ -484,6 +420,7 @@ const fetchLocale = async (url, store, tenant) => { return res; }; + /** * Retrieves the tenant's locale setting from the mod-settings. * @@ -496,15 +433,23 @@ const fetchLocale = async (url, store, tenant) => { * @returns {Promise} A promise that resolves with the locale configuration data. */ const getTenantLocale = async (url, store, tenant) => { - const query = `scope=="${settings.SCOPE}" and key=="${tenantLocaleConfig.KEY}"`; + const response = await fetch(`${url}/locale`, { + headers: getHeaders(tenant, store.getState().okapi.token), + credentials: 'include', + mode: 'cors', + }); - const res = await fetchLocale( - `${url}/settings/entries?query=(${query})`, - store, - tenant - ); + if (response.ok) { + const locale = await response.json(); - return res; + dispatchLocale( + locale, + store, + tenant + ); + } + + return response; }; /** @@ -523,13 +468,27 @@ const getTenantLocale = async (url, store, tenant) => { const getUserOwnLocale = async (url, store, tenant, userId) => { const query = `userId=="${userId}" and scope=="${settings.SCOPE}" and key=="${userOwnLocaleConfig.KEY}"`; - const res = await fetchLocale( - `${url}/settings/entries?query=(${query})`, - store, - tenant - ); + const response = await fetch(`${url}/settings/entries?query=(${query})`, { + headers: getHeaders(tenant, store.getState().okapi.token), + credentials: 'include', + mode: 'cors', + }); - return res; + if (response.ok) { + const json = await response.json(); + + if (json.items?.length) { + const localeValues = JSON.parse(json.items[0]?.value); + + dispatchLocale( + localeValues, + store, + tenant + ); + } + } + + return response; }; /** @@ -584,7 +543,7 @@ export const getFullLocale = (languageRegion, numberingSystem) => { * @returns {Promise} */ const processLocaleSettings = async (store, tenantLocaleData, userLocaleData) => { - const tenantLocaleSettings = tenantLocaleData?.items[0]?.value; + const tenantLocaleSettings = tenantLocaleData; const userLocaleSettings = userLocaleData?.items[0]?.value; const locale = userLocaleSettings?.locale || tenantLocaleSettings?.locale; @@ -598,15 +557,6 @@ const processLocaleSettings = async (store, tenantLocaleData, userLocaleData) => return res; }; -// This function is used to support the deprecated mod-configuration API. -// It is only used when the new mod-settings API returns empty settings. -const getLocalesPromise = (url, store, tenant, userId) => { - return Promise.all([ - getLocale(url, store, tenant), - getUserLocale(url, store, tenant, userId), - ]); -}; - /** * loadResources * return a promise that retrieves the tenant's locale, user's locale, @@ -642,8 +592,8 @@ export async function loadResources(store, tenant, userId) { getTenantLocale(okapiUrl, store, tenant), getUserOwnLocale(okapiUrl, store, tenant, userId), ]); - const [tenantLocaleData, userLocaleData] = await Promise.all(responses.map(res => res.value?.json?.())); - hasSetting = tenantLocaleData?.items[0] || userLocaleData?.items[0]; + const [tenantLocaleData, userLocaleData] = [responses]; + hasSetting = tenantLocaleData || userLocaleData?.items[0].value; if (hasSetting) { await processLocaleSettings(store, tenantLocaleData, userLocaleData); @@ -651,11 +601,6 @@ export async function loadResources(store, tenant, userId) { } } - // only read from legacy mod-config if we haven't already read from mod-settings - if (hasReadConfigPerm && !hasSetting) { - promises.push(getLocalesPromise(okapiUrl, store, tenant, userId)); - } - // tenant's locale, plugin, bindings, and user's locale are all stored // in mod-configuration so we can only retrieve them if the user has // read-permission for configuration entries. diff --git a/src/loginServices.test.js b/src/loginServices.test.js index 8853aeee3..192b5f847 100644 --- a/src/loginServices.test.js +++ b/src/loginServices.test.js @@ -3,13 +3,11 @@ import localforage from 'localforage'; import { createOkapiSession, getBindings, - getLocale, getLoginTenant, getPlugins, getOkapiSession, getTokenExpiry, getUnauthorizedPathFromSession, - getUserLocale, handleLoginError, loadTranslations, logout, @@ -662,36 +660,6 @@ describe('logout', () => { }); }); -describe('getLocale', () => { - it('dispatches setTimezone, setCurrency', async () => { - const value = { timezone: 'America/New_York', currency: 'USD' }; - mockFetchSuccess({ configs: [{ value: JSON.stringify(value) }] }); - const store = { - dispatch: jest.fn(), - getState: () => ({ okapi: {} }), - }; - await getLocale('url', store, 'tenant'); - expect(store.dispatch).toHaveBeenCalledWith(setTimezone(value.timezone)); - expect(store.dispatch).toHaveBeenCalledWith(setCurrency(value.currency)); - mockFetchCleanUp(); - }); -}); - -describe('getUserLocale', () => { - it('dispatches setTimezone, setCurrency', async () => { - const value = { locale: 'en-US', timezone: 'America/New_York', currency: 'USD' }; - mockFetchSuccess({ configs: [{ value: JSON.stringify(value) }] }); - const store = { - dispatch: jest.fn(), - getState: () => ({ okapi: {} }), - }; - await getUserLocale('url', store, 'tenant'); - expect(store.dispatch).toHaveBeenCalledWith(setTimezone(value.timezone)); - expect(store.dispatch).toHaveBeenCalledWith(setCurrency(value.currency)); - mockFetchCleanUp(); - }); -}); - describe('getPlugins', () => { it('dispatches setPlugins', async () => { const configs = [ @@ -905,15 +873,10 @@ describe('loadResources', () => { let loadResourcesResult; const tenantLocaleData = { - items: [{ - id: 'tenantDataId', - value: { - locale: 'en-US', - numberingSystem: 'latn', - timezone: 'America/New_York', - currency: 'USD', - }, - }], + locale: 'en-US', + numberingSystem: 'latn', + timezone: 'America/New_York', + currency: 'USD', }; const userLocaleData = { @@ -930,7 +893,7 @@ describe('loadResources', () => { }; const getResponseData = (url) => { - if (url?.includes('key=="tenantLocaleSettings"')) return tenantLocaleData; + if (url?.includes('/locale')) return tenantLocaleData; if (url?.includes('key=="localeSettings"')) return userLocaleData; return { url }; @@ -978,11 +941,11 @@ describe('loadResources', () => { })); }); - it('should fetch the tenant and user locale settings from mod-settings', async () => { + it('should fetch the tenant and user locale settings from mod-settings locale API', async () => { await loadResources(store, 'tenant', 'userId'); expect(global.fetch).toHaveBeenCalledWith( - 'http://okapi-url/settings/entries?query=(scope=="stripes-core.prefs.manage" and key=="tenantLocaleSettings")', + 'http://okapi-url/locale', expect.anything(), ); expect(global.fetch).toHaveBeenCalledWith( @@ -1021,7 +984,7 @@ describe('loadResources', () => { loadResourcesResult = await loadResources(store, 'tenant', 'userId'); expect(loadResourcesResult.map(({ url }) => url)).toEqual([ - 'http://okapi-url/settings/entries?query=(scope=="stripes-core.prefs.manage" and key=="tenantLocaleSettings")', + 'http://okapi-url/locale', 'http://okapi-url/settings/entries?query=(userId=="userId" and scope=="stripes-core.prefs.manage" and key=="localeSettings")', 'http://okapi-url/configurations/entries?query=(module==PLUGINS)', 'http://okapi-url/configurations/entries?query=(module==ORG and configName==bindings)', @@ -1049,80 +1012,6 @@ describe('loadResources', () => { }); }); }); - - describe('when the user or tenant locale settings are not present in mod-settings', () => { - const getData = (url) => { - // if mod-settings API - if (url?.includes('key=="tenantLocaleSettings"') || url?.includes('key=="localeSettings"')) { - return { url, items: [] }; - } - - // if mod-configuration API - if (url?.includes('configName == localeSettings') || url?.includes('"configName"=="localeSettings"')) { - return { - url, - configs: [{ - value: JSON.stringify({ - locale: 'en-GB-u-nu-latn', - timezone: 'UTC', - currency: 'USD' - }), - }], - }; - } - - return { url }; - }; - - beforeEach(() => { - global.fetch = jest.fn().mockImplementation((url) => Promise.resolve({ - url, - json: () => Promise.resolve(getData(url)), - ok: true, - })); - }); - - it('should fetch the tenant and user locale settings from mod-settings and mod-configuration', async () => { - await loadResources(store, 'tenant', 'userId'); - - expect(global.fetch).toHaveBeenCalledWith( - 'http://okapi-url/settings/entries?query=(scope=="stripes-core.prefs.manage" and key=="tenantLocaleSettings")', - expect.anything(), - ); - expect(global.fetch).toHaveBeenCalledWith( - 'http://okapi-url/settings/entries?query=(userId=="userId" and scope=="stripes-core.prefs.manage" and key=="localeSettings")', - expect.anything(), - ); - expect(global.fetch).toHaveBeenCalledWith( - 'http://okapi-url/configurations/entries?query=(module==ORG AND configName == localeSettings AND (cql.allRecords=1 NOT userId="" NOT code=""))', - expect.anything(), - ); - expect(global.fetch).toHaveBeenCalledWith( - 'http://okapi-url/configurations/entries?query=("configName"=="localeSettings" AND "module"=="@folio/stripes-core" and userId=="userId")', - expect.anything(), - ); - }); - - it('should apply locale settings from mod-configuration', async () => { - await loadResources(store, 'tenant', 'userId'); - - expect(store.dispatch).toHaveBeenCalledWith(setTimezone('UTC')); - expect(store.dispatch).toHaveBeenCalledWith(setCurrency('USD')); - expect(document.documentElement.lang).toBe('en-GB-u-nu-latn'); - }); - - it('should retrieve tenant-locale, user-locale, plugins, and bindings from configurations', async () => { - loadResourcesResult = await loadResources(store, 'tenant', 'userId'); - - expect(loadResourcesResult.map(({ url }) => url)).toEqual([ - 'http://okapi-url/configurations/entries?query=(module==ORG AND configName == localeSettings AND (cql.allRecords=1 NOT userId="" NOT code=""))', - 'http://okapi-url/configurations/entries?query=("configName"=="localeSettings" AND "module"=="@folio/stripes-core" and userId=="userId")', - 'http://okapi-url/configurations/entries?query=(module==PLUGINS)', - 'http://okapi-url/configurations/entries?query=(module==ORG and configName==bindings)', - 'discoverServices', - ]); - }); - }); }); describe('when there is permission to only read tenant settings from mod-settings', () => { @@ -1153,7 +1042,7 @@ describe('loadResources', () => { await loadResources(store, 'tenant', 'userId'); expect(global.fetch).toHaveBeenCalledWith( - 'http://okapi-url/settings/entries?query=(scope=="stripes-core.prefs.manage" and key=="tenantLocaleSettings")', + 'http://okapi-url/locale', expect.anything(), ); expect(global.fetch).toHaveBeenCalledWith( @@ -1176,8 +1065,8 @@ describe('loadResources', () => { }); it('should apply tenant locale settings', async () => { - const timezone = tenantLocaleData.items[0].value.timezone; - const currency = tenantLocaleData.items[0].value.currency; + const timezone = tenantLocaleData.timezone; + const currency = tenantLocaleData.currency; await loadResources(store, 'tenant', 'userId'); @@ -1190,7 +1079,7 @@ describe('loadResources', () => { loadResourcesResult = await loadResources(store, 'tenant', 'userId'); expect(loadResourcesResult.map(({ url } = {}) => url)).toEqual([ - 'http://okapi-url/settings/entries?query=(scope=="stripes-core.prefs.manage" and key=="tenantLocaleSettings")', + 'http://okapi-url/locale', undefined, // rejected request for user locale 'discoverServices', ]); @@ -1237,7 +1126,7 @@ describe('loadResources', () => { await loadResources(store, 'tenant', 'userId'); expect(global.fetch).not.toHaveBeenCalledWith( - 'http://okapi-url/settings/entries?query=(scope=="stripes-core.prefs.manage" and key=="tenantLocaleSettings")', + 'http://okapi-url/locale', expect.anything(), ); expect(global.fetch).not.toHaveBeenCalledWith( @@ -1246,19 +1135,6 @@ describe('loadResources', () => { ); }); - it('should fetch the tenant and user locale settings from mod-configuration', async () => { - await loadResources(store, 'tenant', 'userId'); - - expect(global.fetch).toHaveBeenCalledWith( - 'http://okapi-url/configurations/entries?query=(module==ORG AND configName == localeSettings AND (cql.allRecords=1 NOT userId="" NOT code=""))', - expect.anything(), - ); - expect(global.fetch).toHaveBeenCalledWith( - 'http://okapi-url/configurations/entries?query=("configName"=="localeSettings" AND "module"=="@folio/stripes-core" and userId=="userId")', - expect.anything(), - ); - }); - it('should fetch the plugins and bindings', async () => { await loadResources(store, 'tenant', 'userId'); @@ -1272,20 +1148,10 @@ describe('loadResources', () => { ); }); - it('should apply locale settings', async () => { - await loadResources(store, 'tenant', 'userId'); - - expect(store.dispatch).toHaveBeenCalledWith(setTimezone('UTC')); - expect(store.dispatch).toHaveBeenCalledWith(setCurrency('USD')); - expect(document.documentElement.lang).toBe('en-GB-u-nu-latn'); - }); - - it('should retrieve tenant-locale, user-locale, plugins, and bindings from configurations', async () => { + it('should retrieve plugins and bindings from configurations', async () => { loadResourcesResult = await loadResources(store, 'tenant', 'userId'); expect(loadResourcesResult.map(({ url }) => url)).toEqual([ - 'http://okapi-url/configurations/entries?query=(module==ORG AND configName == localeSettings AND (cql.allRecords=1 NOT userId="" NOT code=""))', - 'http://okapi-url/configurations/entries?query=("configName"=="localeSettings" AND "module"=="@folio/stripes-core" and userId=="userId")', 'http://okapi-url/configurations/entries?query=(module==PLUGINS)', 'http://okapi-url/configurations/entries?query=(module==ORG and configName==bindings)', 'discoverServices',