diff --git a/Dockerfile b/Dockerfile index 7fda35f35..9a19e32c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ LABEL name="Entando App Builder" \ maintainer="dev@entando.com" \ vendor="Entando Inc." \ version="v${VERSION}" \ - release="7.3.0" \ + release="7.5.0" \ summary="Entando App Builder" \ description="The Entando App Builder is the front end environment to interact with the micro frontends, the WCMS, and other Entando components" diff --git a/package-lock.json b/package-lock.json index c29656a78..2e3e41ff5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@entando/app-builder", - "version": "7.3.0", + "version": "7.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5104,7 +5104,7 @@ "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "dev": true, "requires": { "callsites": "^2.0.0" @@ -5113,7 +5113,7 @@ "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true } } @@ -6824,7 +6824,7 @@ "dotenv": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", - "integrity": "sha512-XcaMACOr3JMVcEv0Y/iUM2XaOsATRZ3U1In41/1jjK6vJZ2PZbQ1bzCG8uvaByfaBpl9gqc9QWJovpUGBXLLYQ==" + "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=" }, "dotenv-expand": { "version": "4.2.0", @@ -10884,7 +10884,7 @@ "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { "error-ex": "^1.3.1", @@ -10894,7 +10894,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pkg-dir": { @@ -10909,7 +10909,7 @@ "read-pkg": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", - "integrity": "sha512-+UBirHHDm5J+3WDmLBZYSklRYg82nMlz+enn+GMZ22nSR2f4bzxmhso6rzQW/3mT2PVzpzDTiYIZahk8UmZ44w==", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", "dev": true, "requires": { "normalize-package-data": "^2.3.2", @@ -10968,7 +10968,7 @@ "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "dev": true, "requires": { "caller-path": "^2.0.0", @@ -10978,7 +10978,7 @@ "caller-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "dev": true, "requires": { "caller-callsite": "^2.0.0" @@ -10987,7 +10987,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true } } @@ -19132,7 +19132,7 @@ "semver-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, "semver-diff": { diff --git a/package.json b/package.json index 70cf57b24..3bf958c10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@entando/app-builder", - "version": "7.3.0", + "version": "7.5.0", "author": "Entando", "homepage": "https://github.com/entando/app-builder", "license": "MIT", diff --git a/src/api/pages.js b/src/api/pages.js index bd3d3246f..1c5e26703 100644 --- a/src/api/pages.js +++ b/src/api/pages.js @@ -45,6 +45,13 @@ export const getPage = (pageCode, status = PAGE_STATUS_DRAFT) => makeRequest({ ), }); +export const getRootPage = () => makeRequest({ + uri: '/api/pages/utils/root', + method: METHODS.GET, + mockResponse: HOMEPAGE_PAYLOAD, + useAuthentication: true, +}); + export const getPageSEO = pageCode => makeRequest({ uri: `/api/plugins/seo/pages/${pageCode}`, method: METHODS.GET, @@ -82,7 +89,7 @@ export const getPageChildren = pageCode => makeRequest({ }); export const getViewPages = () => makeRequest({ - uri: '/api/pages/viewpages', + uri: '/api/pages/utils/viewpages', method: METHODS.GET, mockResponse: VIEWPAGES_PAYLOAD, contentType: 'application/json', @@ -175,7 +182,7 @@ export const putPageStatus = (pageCode, status) => makeRequest({ }); export const getFreePages = () => makeRequest({ - uri: '/api/pages/search/group/free', + uri: '/api/pages/utils/search/group/free', method: METHODS.GET, mockResponse: FREE_PAGES_PAYLOAD, useAuthentication: true, @@ -199,7 +206,7 @@ export const putPageSettings = pageSettings => makeRequest({ export const getSearchPages = (page = { page: 1, pageSize: 10 }, params = '') => makeRequest( { - uri: `/api/pages/search${params}`, + uri: `/api/pages/utils/search${params}`, method: METHODS.GET, useAuthentication: true, mockResponse: SEARCH_PAGES, diff --git a/src/app-init/apiManager.js b/src/app-init/apiManager.js index 379d8ad08..d4b6f5f10 100644 --- a/src/app-init/apiManager.js +++ b/src/app-init/apiManager.js @@ -7,6 +7,7 @@ import { clearLoggedUserPermissions, } from 'state/permissions/actions'; import { clearAppTourProgress } from 'state/app-tour/actions'; +import { setLoading } from 'state/loading/actions'; import { addToast, TOAST_WARNING } from '@entando/messages'; import { defineMessages, injectIntl, intlShape } from 'react-intl'; import { history, ROUTE_DASHBOARD, ROUTE_HOME } from 'app-init/router'; @@ -40,6 +41,7 @@ const ApiManager = ({ auth.setToRefreshToken(false); } else { const { redirectUri, pathname } = opts; + store.dispatch(setLoading('rootPage', false)); store.dispatch(fetchPermissions()) .then(() => store.dispatch(fetchLoggedUserPermissions())); if (redirectUri) { diff --git a/src/state/pages/actions.js b/src/state/pages/actions.js index bd0cd9523..09052db89 100644 --- a/src/state/pages/actions.js +++ b/src/state/pages/actions.js @@ -4,7 +4,7 @@ import { addToast, addErrors, TOAST_SUCCESS, TOAST_ERROR } from '@entando/messag import { setPage } from 'state/pagination/actions'; import { getPage, getPageChildren, setPagePosition, postPage, deletePage, getFreePages, - getPageSettings, putPage, putPageStatus, getViewPages, getSearchPages, + getPageSettings, putPage, putPageStatus, getViewPages, getSearchPages, getRootPage, putPageSettings, patchPage, getPageSEO, postPageSEO, putPageSEO, postClonePage, } from 'api/pages'; import { @@ -13,6 +13,7 @@ import { getChildrenMap, getSelectedPage, getAllPageTreeLoadedStatus, + getRootPageCode, } from 'state/pages/selectors'; import { makeGetSelectedPageConfig } from 'state/page-config/selectors'; import { setPublishedPageConfig } from 'state/page-config/actions'; @@ -21,13 +22,14 @@ import { MOVE_PAGE, SET_FREE_PAGES, SET_SELECTED_PAGE, REMOVE_PAGE, UPDATE_PAGE, SEARCH_PAGES, CLEAR_SEARCH, SET_REFERENCES_SELECTED_PAGE, CLEAR_TREE, BATCH_TOGGLE_EXPANDED, COLLAPSE_ALL, SET_DASHBOARD_PAGES, - SET_VIRTUAL_ROOT, + SET_VIRTUAL_ROOT, SET_ROOT_PAGE, } from 'state/pages/types'; -import { HOMEPAGE_CODE, PAGE_STATUS_DRAFT, PAGE_STATUS_PUBLISHED, PAGE_STATUS_UNPUBLISHED, SEO_ENABLED } from 'state/pages/const'; +import { PAGE_STATUS_DRAFT, PAGE_STATUS_PUBLISHED, PAGE_STATUS_UNPUBLISHED, SEO_ENABLED } from 'state/pages/const'; import { history, ROUTE_PAGE_TREE, ROUTE_PAGE_CLONE, ROUTE_PAGE_ADD } from 'app-init/router'; import { generateJsonPatch } from 'helpers/jsonPatch'; import getSearchParam from 'helpers/getSearchParam'; -import { toggleLoading } from 'state/loading/actions'; +import { toggleLoading, setLoading } from 'state/loading/actions'; +import { getLoading } from 'state/loading/selectors'; import { getDefaultLanguage } from 'state/languages/selectors'; import { APP_TOUR_CANCELLED, APP_TOUR_STARTED, APP_TOUR_HOMEPAGE_CODEREF } from 'state/app-tour/const'; @@ -176,6 +178,11 @@ export const setVirtualRoot = virtualRoot => ({ payload: virtualRoot, }); +export const setRootPage = rootPageCode => ({ + type: SET_ROOT_PAGE, + payload: rootPageCode, +}); + const wrapApiCall = apiFunc => (...args) => async (dispatch) => { const response = await apiFunc(...args); const json = await response.json(); @@ -198,6 +205,21 @@ export const fetchIfPageExists = pageCode => new Promise((resolve) => { getPage(pageCode).then(response => resolve(response.ok)).catch(() => resolve(false)); }); +export const fetchRootPage = () => async (dispatch, getState) => { + if (getLoading(getState()).rootPage) return; + dispatch(setLoading('rootPage', true)); + try { + const response = await getRootPage(); + const json = await response.json(); + if (response.ok) { + dispatch(setRootPage(json.payload.code)); + } + } catch (e) { + dispatch(setLoading('rootPage', false)); + // falls back to 'homepage' default in reducer + } +}; + export const fetchViewPages = () => dispatch => new Promise((resolve) => { getViewPages().then((response) => { @@ -240,8 +262,9 @@ export const sendDeletePage = (page, successRedirect = true) => async (dispatch) } }; -export const fetchPageTree = pageCode => async (dispatch) => { - if (pageCode === HOMEPAGE_CODE) { +export const fetchPageTree = pageCode => async (dispatch, getState) => { + const rootPageCode = getRootPageCode(getState()); + if (pageCode === rootPageCode) { const responses = await Promise.all([ fetchPage(pageCode)(dispatch), fetchPageChildren(pageCode)(dispatch), @@ -256,21 +279,22 @@ export const fetchPageTree = pageCode => async (dispatch) => { }; -export const handleExpandPage = (pageCode = HOMEPAGE_CODE, alwaysExpand) => ( +export const handleExpandPage = (pageCode, alwaysExpand) => ( (dispatch, getState) => { const state = getState(); - const pageStatus = getStatusMap(state)[pageCode]; + const effectivePageCode = pageCode || getRootPageCode(state); + const pageStatus = getStatusMap(state)[effectivePageCode]; const toExpand = (!pageStatus || !pageStatus.expanded); const toLoad = (toExpand && (!pageStatus || pageStatus.expanded === undefined)); if (toLoad) { - dispatch(setPageLoading(pageCode)); - return fetchPageTree(pageCode)(dispatch) + dispatch(setPageLoading(effectivePageCode)); + return fetchPageTree(effectivePageCode)(dispatch, getState) .then((pages) => { dispatch(addPages(pages)); - dispatch(setPageExpanded(pageCode, true)); - dispatch(setPageLoaded(pageCode)); + dispatch(setPageExpanded(effectivePageCode, true)); + dispatch(setPageLoaded(effectivePageCode)); if ( - pageCode === APP_TOUR_HOMEPAGE_CODEREF && + effectivePageCode === APP_TOUR_HOMEPAGE_CODEREF && getAppTourProgress(state) !== APP_TOUR_CANCELLED ) { dispatch(setExistingPages(pages)); @@ -278,7 +302,7 @@ export const handleExpandPage = (pageCode = HOMEPAGE_CODE, alwaysExpand) => ( }).catch(() => {}); } dispatch(setPageExpanded( - pageCode, + effectivePageCode, alwaysExpand !== undefined ? alwaysExpand : toExpand, )); return noopPromise(); @@ -320,7 +344,7 @@ const movePage = (pageCode, siblingCode, moveAbove) => (dispatch, getState) => { const siblingPage = getPagesMap(state)[siblingCode]; const page = getPagesMap(state)[pageCode]; const oldParentCode = page.parentCode; - const newParentCode = siblingPage.parentCode || HOMEPAGE_CODE; + const newParentCode = siblingPage.parentCode || getRootPageCode(state); const newSiblingChildren = getChildrenMap(state)[newParentCode] .filter(code => code !== pageCode); const newSiblingIndex = newSiblingChildren.indexOf(siblingCode); @@ -564,8 +588,8 @@ export const fetchPageForm = pageCode => (dispatch, getState) => fetchPageInfo(p }) .catch(() => {}); -export const loadSelectedPage = pageCode => dispatch => - fetchPage(pageCode || getSearchParam('parentCode') || HOMEPAGE_CODE)(dispatch) +export const loadSelectedPage = pageCode => (dispatch, getState) => + fetchPage(pageCode || getSearchParam('parentCode') || getRootPageCode(getState()))(dispatch) .then((response) => { dispatch(setSelectedPage(response.payload)); return response.payload; diff --git a/src/state/pages/reducer.js b/src/state/pages/reducer.js index a6896522c..38ca0f56e 100644 --- a/src/state/pages/reducer.js +++ b/src/state/pages/reducer.js @@ -19,6 +19,7 @@ import { COLLAPSE_ALL, SET_DASHBOARD_PAGES, SET_VIRTUAL_ROOT, + SET_ROOT_PAGE, } from 'state/pages/types'; // creates a map from an array @@ -294,6 +295,14 @@ export const virtualRoot = (state = false, action = {}) => { } }; +export const rootPage = (state = 'homepage', action = {}) => { + switch (action.type) { + case SET_ROOT_PAGE: + return action.payload; + default: return state; + } +}; + export default combineReducers({ map: reducer, childrenMap, @@ -305,4 +314,5 @@ export default combineReducers({ search, dashboard, virtualRoot, + rootPage, }); diff --git a/src/state/pages/selectors.js b/src/state/pages/selectors.js index f7022afcf..1f1b11c14 100644 --- a/src/state/pages/selectors.js +++ b/src/state/pages/selectors.js @@ -1,7 +1,7 @@ import { createSelector } from 'reselect'; import { getLocale } from 'state/locale/selectors'; -import { HOMEPAGE_CODE, PAGE_STATUS_PUBLISHED } from 'state/pages/const'; +import { PAGE_STATUS_PUBLISHED } from 'state/pages/const'; import { getDomain } from '@entando/apimanager'; import { PREVIEW_NAMESPACE } from 'ui/pages/config/const'; import { get } from 'lodash'; @@ -17,6 +17,7 @@ export const getSelectedPage = state => state.pages.selected; export const getSearchPagesRaw = state => state.pages.search; export const getDashboardPages = state => state.pages.dashboard; export const getIsVirtualRootOn = state => state.pages.virtualRoot; +export const getRootPageCode = state => state.pages.rootPage; export const getSearchPages = createSelector( [getSearchPagesRaw], @@ -41,8 +42,8 @@ export const getFreePages = createSelector( // relies on the children map order -const getPagesOrder = (pagesChildren) => { - const fifo = [HOMEPAGE_CODE]; +const getPagesOrder = (pagesChildren, rootPageCode) => { + const fifo = [rootPageCode]; const sorted = []; while (fifo.length) { const curPageCode = fifo.pop(); @@ -56,10 +57,10 @@ const getPagesOrder = (pagesChildren) => { return sorted; }; -const isVisible = (pageCode, pages, pagesStatus) => { +const isVisible = (pageCode, pages, pagesStatus, rootPageCode) => { let curPageCode = pageCode; if (pages[curPageCode]) { - while (curPageCode !== HOMEPAGE_CODE) { + while (curPageCode !== rootPageCode) { if (pages[curPageCode].parentCode) { curPageCode = pages[curPageCode].parentCode; if (pagesStatus[curPageCode] && !pagesStatus[curPageCode].expanded) { @@ -72,11 +73,11 @@ const isVisible = (pageCode, pages, pagesStatus) => { return false; }; -const getDepth = (pages, pageCode) => { +const getDepth = (pages, pageCode, rootPageCode) => { let curPageCode = pageCode; let depth = 0; if (pages[curPageCode]) { - while (curPageCode !== HOMEPAGE_CODE) { + while (curPageCode !== rootPageCode) { curPageCode = pages[curPageCode].parentCode; depth += 1; } @@ -86,14 +87,14 @@ const getDepth = (pages, pageCode) => { // calculates the position map based on children map export const getPositionMap = createSelector( - [getChildrenMap], - childrenMap => Object.keys(childrenMap).reduce((acc, pageCode) => { + [getChildrenMap, getRootPageCode], + (childrenMap, rootPageCode) => Object.keys(childrenMap).reduce((acc, pageCode) => { const children = childrenMap[pageCode]; children.forEach((childCode, i) => { acc[childCode] = i + 1; }); return acc; - }, { homepage: 1 }), + }, { [rootPageCode]: 1 }), ); @@ -105,10 +106,13 @@ const PAGE_STATUS_DEFAULTS = { export const getPageTreePages = createSelector( [getPagesMap, getChildrenMap, getStatusMap, getTitlesMap, getLocale, getDefaultLanguage, - getIsVirtualRootOn], - (pages, pageChildren, pagesStatus, pagesTitles, locale, defaultLang, virtualRootOn) => ( - getPagesOrder(pageChildren) - .filter(pageCode => isVisible(pageCode, pages, pagesStatus)) + getIsVirtualRootOn, getRootPageCode], + ( + pages, pageChildren, pagesStatus, pagesTitles, locale, + defaultLang, virtualRootOn, rootPageCode, + ) => ( + getPagesOrder(pageChildren, rootPageCode) + .filter(pageCode => isVisible(pageCode, pages, pagesStatus, rootPageCode)) .map((pageCode) => { const isEmpty = !(pageChildren[pageCode] && pageChildren[pageCode].length); let hasPublishedChildren = false; @@ -125,7 +129,7 @@ export const getPageTreePages = createSelector( Object.keys(pagesTitles[pageCode]).find(langCode => pagesTitles[pageCode][langCode]) ]; - if (pageCode === HOMEPAGE_CODE && virtualRootOn) { + if (pageCode === rootPageCode && virtualRootOn) { title = 'Root'; } @@ -133,7 +137,7 @@ export const getPageTreePages = createSelector( ...pages[pageCode], ...PAGE_STATUS_DEFAULTS, ...pagesStatus[pageCode], - depth: getDepth(pages, pageCode), + depth: getDepth(pages, pageCode, rootPageCode), isEmpty, hasPublishedChildren, parentStatus, diff --git a/src/state/pages/types.js b/src/state/pages/types.js index ef30e4e09..48e7ff5ff 100644 --- a/src/state/pages/types.js +++ b/src/state/pages/types.js @@ -17,3 +17,4 @@ export const COLLAPSE_ALL = 'pages/collapse-all-pages'; export const SET_VIEWPAGES = 'pages/set-viewpages'; export const SET_DASHBOARD_PAGES = 'pages/set-dashboard-pages'; export const SET_VIRTUAL_ROOT = 'pages/set-virtual-root'; +export const SET_ROOT_PAGE = 'pages/set-root-page'; diff --git a/src/ui/app/MfeContainer.js b/src/ui/app/MfeContainer.js index ce43f9bf9..316dc513d 100644 --- a/src/ui/app/MfeContainer.js +++ b/src/ui/app/MfeContainer.js @@ -8,6 +8,7 @@ import { getLocale } from 'state/locale/selectors'; import { getLoggedUserPermissions } from 'state/permissions/selectors'; import { getDomain } from 'helpers/resourcePath'; import { getSystemReport } from 'state/system/selectors'; +import { getRootPageCode } from 'state/pages/selectors'; import { useDynamicResourceUrl } from 'hooks/useDynamicResourceUrl'; import { selectCurrSystemConfigAdvancedSearch } from 'state/current-system-configuration/selectors'; import { getUserPreferences } from 'state/user-preferences/selectors'; @@ -20,6 +21,7 @@ const MfeContainer = ({ id, history }) => { const currentSystemConfigurationAdvancedSearchOn = useSelector(selectCurrSystemConfigAdvancedSearch); const userPreferences = useSelector(getUserPreferences) || {}; + const rootPageCode = useSelector(getRootPageCode); const mfeResourceBasePath = useDynamicResourceUrl(mfe.assetsBasePath); @@ -35,6 +37,7 @@ const MfeContainer = ({ id, history }) => { systemReport, advancedSearchOn: currentSystemConfigurationAdvancedSearchOn, disableContentMenu: userPreferences.disableContentMenu, + rootPageCode, }; if (JSON.stringify(entandoWindow.globals || {}) !== JSON.stringify(globals)) { @@ -48,7 +51,7 @@ const MfeContainer = ({ id, history }) => { window.entando = entandoWindow; }, [history, locale, mfe.assetsBasePath, mfe.widgetName, permissions, systemReport, mfeResourceBasePath, currentSystemConfigurationAdvancedSearchOn, - userPreferences.disableContentMenu]); + userPreferences.disableContentMenu, rootPageCode]); const params = { config: { diff --git a/src/ui/internal-page/VerticalMenuContainer.js b/src/ui/internal-page/VerticalMenuContainer.js index 311cb5cc1..d2f14cc85 100644 --- a/src/ui/internal-page/VerticalMenuContainer.js +++ b/src/ui/internal-page/VerticalMenuContainer.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { connect, useSelector } from 'react-redux'; +import { connect, useSelector, useDispatch } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { injectIntl, intlShape } from 'react-intl'; import { VerticalNav, Button, Icon } from 'patternfly-react'; @@ -36,10 +36,11 @@ import { import { withPermissionValues } from 'ui/auth/withPermissions'; import InfoMenu from 'ui/internal-page/InfoMenu'; // import getRuntimeEnv from 'helpers/getRuntimeEnv'; -import { HOMEPAGE_CODE } from 'state/pages/const'; +import { getRootPageCode } from 'state/pages/selectors'; import useLocalStorage from 'helpers/useLocalStorage'; import { getSystemReport } from 'state/system/selectors'; import { fetchSystemReport } from 'state/system/actions'; +import { fetchRootPage } from 'state/pages/actions'; import { dismissedWizardKey } from 'ui/app-tour/constant'; import { getMfeTargetPrimaryMenu } from 'state/mfe/selectors'; import MfeContainer from 'ui/app/MfeContainer'; @@ -201,6 +202,7 @@ const EntandoMenu = ({ const [collapsed, setCollapsed] = useLocalStorage('navCollapsed', false); const systemReport = useSelector(getSystemReport); const currSystemConfigAdvancedSearchOn = useSelector(selectCurrSystemConfigAdvancedSearch); + const rootPageCode = useSelector(getRootPageCode); useEffect(() => { onMount(); @@ -272,7 +274,7 @@ const EntandoMenu = ({ id="menu-page-config" title={intl.formatMessage({ id: 'menu.pageConfig', defaultMessage: 'Designer' })} onClick={() => - history.push(routeConverter(ROUTE_PAGE_CONFIG, { pageCode: HOMEPAGE_CODE })) + history.push(routeConverter(ROUTE_PAGE_CONFIG, { pageCode: rootPageCode })) } /> { @@ -470,6 +472,12 @@ const VerticalMenu = (props) => { const mfeMenu = useSelector(getMfeTargetPrimaryMenu); // const mfeHeaderMenu = useSelector(getMfeTargetPrimaryHeader); // const isPrimaryTenant = useSelector(selectIsPrimaryTenant); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchRootPage()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // TODO: remove when we have the ECR API is implemented const isMFEMenuEnabled = process.env.USE_MFE || false; @@ -532,6 +540,7 @@ const mapDispatchToProps = (dispatch, { history }) => ({ }, onMount: () => { dispatch(fetchSystemReport()); + dispatch(fetchRootPage()); }, }); diff --git a/src/ui/pages/common/PageTree.js b/src/ui/pages/common/PageTree.js index 635fc8295..0602da2ad 100644 --- a/src/ui/pages/common/PageTree.js +++ b/src/ui/pages/common/PageTree.js @@ -16,14 +16,14 @@ import PublishPageModalContainer from 'ui/pages/common/PublishPageModalContainer import UnpublishPageModalContainer from 'ui/pages/common/UnpublishPageModalContainer'; import PageListSearchTable from 'ui/pages/list/PageListSearchTable'; import MovePageModalContainer from 'ui/pages/common/MovePageModalContainer'; -import { HOMEPAGE_CODE, PAGE_MOVEMENT_OPTIONS } from 'state/pages/const'; +import { PAGE_MOVEMENT_OPTIONS } from 'state/pages/const'; -export const getIsRootAndVirtual = (page, virtualRootOn) => { +export const getIsRootAndVirtual = (page, virtualRootOn, rootPageCode) => { if (!page) { return false; } - if (page.code === HOMEPAGE_CODE && virtualRootOn) { + if (page.code === rootPageCode && virtualRootOn) { return true; } return false; @@ -52,6 +52,7 @@ class PageTree extends Component { onCollapseAll, onExpandPage, virtualRootOn, + rootPageCode, } = this.props; const columnDefs = { @@ -117,7 +118,7 @@ class PageTree extends Component { className.push('PageTree__tree-column-td--empty'); } // No drag class is added if first level child and Virtual Root On - if (page.original.parentCode === HOMEPAGE_CODE && virtualRootOn) { + if (page.original.parentCode === rootPageCode && virtualRootOn) { className.push('PageTree__no-drag'); } @@ -169,7 +170,8 @@ class PageTree extends Component { } renderActionCell({ original: page }) { - const isRootAndVirtual = getIsRootAndVirtual(page, this.props.virtualRootOn); + const { virtualRootOn, rootPageCode } = this.props; + const isRootAndVirtual = getIsRootAndVirtual(page, virtualRootOn, rootPageCode); if (isRootAndVirtual) { return null; @@ -294,6 +296,7 @@ PageTree.propTypes = { myGroupIds: PropTypes.arrayOf(PropTypes.string), virtualRootOn: PropTypes.bool, getIsVirtualRootOn: PropTypes.bool, + rootPageCode: PropTypes.string, }; PageTree.defaultProps = { @@ -308,6 +311,7 @@ PageTree.defaultProps = { myGroupIds: [], virtualRootOn: false, getIsVirtualRootOn: false, + rootPageCode: null, }; export default PageTree; diff --git a/src/ui/pages/common/PageTreeContainer.js b/src/ui/pages/common/PageTreeContainer.js index 9a5572bed..e2aa19107 100644 --- a/src/ui/pages/common/PageTreeContainer.js +++ b/src/ui/pages/common/PageTreeContainer.js @@ -29,7 +29,7 @@ import { import { setColumnOrder } from 'state/table-column-order/actions'; import { getColumnOrder } from 'state/table-column-order/selectors'; -import { getIsVirtualRootOn, getPageTreePages, getSearchPages } from 'state/pages/selectors'; +import { getIsVirtualRootOn, getPageTreePages, getSearchPages, getRootPageCode } from 'state/pages/selectors'; import { PAGE_INIT_VALUES } from 'ui/pages/common/const'; import { setAppTourLastStep } from 'state/app-tour/actions'; import { getDomain } from '@entando/apimanager'; @@ -48,6 +48,7 @@ export const mapStateToProps = state => ({ pageSearchColumnOrder: getColumnOrder(state, 'pageSearch'), myGroupIds: getMyGroupsList(state), virtualRootOn: getIsVirtualRootOn(state), + rootPageCode: getRootPageCode(state), }); export const mapDispatchToProps = (dispatch, ownProps) => ({ diff --git a/src/ui/pages/list/PageTreePageContainer.js b/src/ui/pages/list/PageTreePageContainer.js index 36ca3b39e..2b4db6002 100644 --- a/src/ui/pages/list/PageTreePageContainer.js +++ b/src/ui/pages/list/PageTreePageContainer.js @@ -4,7 +4,7 @@ import { clearErrors } from '@entando/messages'; import { formValueSelector } from 'redux-form'; import PageTreePage from 'ui/pages/list/PageTreePage'; -import { handleExpandPage, fetchSearchPages, clearSearchPage, clearTree } from 'state/pages/actions'; +import { handleExpandPage, fetchSearchPages, clearSearchPage, clearTree, fetchRootPage } from 'state/pages/actions'; import { getLocale } from 'state/locale/selectors'; import { getSearchPages } from 'state/pages/selectors'; import { toggleLoading } from 'state/loading/actions'; @@ -30,7 +30,8 @@ export const mapDispatchToProps = dispatch => ({ dispatch(clearTree()); dispatch(clearSearchPage()); dispatch(toggleLoading('pageTree')); - dispatch(handleExpandPage()) + dispatch(fetchRootPage()) + .then(() => dispatch(handleExpandPage())) .then(() => { if (appTourProgress === APP_TOUR_STARTED && isSuperuser) { dispatch(handleExpandPage(APP_TOUR_HOMEPAGE_CODEREF)).finally(() => dispatch(toggleLoading('pageTree'))); diff --git a/src/ui/widgets/config/forms/NavigationBarConfigFormContainer.js b/src/ui/widgets/config/forms/NavigationBarConfigFormContainer.js index 0926b43c1..bb77b8b99 100644 --- a/src/ui/widgets/config/forms/NavigationBarConfigFormContainer.js +++ b/src/ui/widgets/config/forms/NavigationBarConfigFormContainer.js @@ -7,7 +7,7 @@ import NavigationBarConfigForm from 'ui/widgets/config/forms/NavigationBarConfig import { fetchSearchPages } from 'state/pages/actions'; import { fetchLanguages } from 'state/languages/actions'; import { getLocale } from 'state/locale/selectors'; -import { getSearchPages } from 'state/pages/selectors'; +import { getSearchPages, getRootPageCode } from 'state/pages/selectors'; import { updateConfiguredPageWidget } from 'state/widget-config/actions'; import { setVisibleModal } from 'state/modal/actions'; @@ -18,7 +18,6 @@ import { getLoading } from 'state/loading/selectors'; import { getAppTourProgress } from 'state/app-tour/selectors'; import { APP_TOUR_STARTED } from 'state/app-tour/const'; import { setAppTourLastStep } from 'state/app-tour/actions'; -import { HOMEPAGE_CODE } from 'state/pages/const'; export const NavigationBarWidgetID = 'navigationBarWidgetForm'; @@ -31,12 +30,13 @@ export const mapStateToProps = (state, ownProps) => ({ expressions: formValueSelector(NavigationBarWidgetID)(state, 'expressions'), loading: getLoading(state).expressionList, appTourProgress: getAppTourProgress(state), + rootPageCode: getRootPageCode(state), }); export const mapDispatchToProps = (dispatch, ownProps) => ({ - onDidMount: ({ initialize, appTourProgress }) => { + onDidMount: ({ initialize, appTourProgress, rootPageCode }) => { if (appTourProgress === APP_TOUR_STARTED) { - dispatch(initialize({ addConfig: { spec: 'code', targetCode: HOMEPAGE_CODE } })); + dispatch(initialize({ addConfig: { spec: 'code', targetCode: rootPageCode } })); } dispatch(fetchLanguages({ page: 1, pageSize: 0 })); dispatch(fetchSearchPages({ page: 1, pageSize: 0 })); diff --git a/test/api/pages.test.js b/test/api/pages.test.js index 5397f4c84..eb8129ab3 100644 --- a/test/api/pages.test.js +++ b/test/api/pages.test.js @@ -1,6 +1,6 @@ import 'test/enzyme-init'; import { - getPage, getPageChildren, setPagePosition, postPage, putPage, patchPage, deletePage, + getPage, getRootPage, getPageChildren, setPagePosition, postPage, putPage, patchPage, deletePage, getSearchPages, getPageSettings, getFreePages, getPageConfig, deletePageWidget, putPageWidget, getReferencesPage, restorePageConfig, applyDefaultPageConfig, putPageSettings, } from 'api/pages'; @@ -51,6 +51,20 @@ describe('api/pages', () => { }); }); + describe('getRootPage', () => { + it('returns a promise', () => { + expect(getRootPage()).toBeInstanceOf(Promise); + }); + it('makes the correct request', () => { + getRootPage(); + expect(makeRequest).toHaveBeenCalledWith(expect.objectContaining({ + uri: '/api/pages/utils/root', + method: METHODS.GET, + useAuthentication: true, + })); + }); + }); + describe('getPageChildren', () => { it('returns a promise', () => { expect(getPageChildren(PAGE_CODE)).toBeInstanceOf(Promise); @@ -201,7 +215,7 @@ describe('api/pages', () => { getSearchPages(); expect(makeRequest).toHaveBeenCalledWith( expect.objectContaining({ - uri: '/api/pages/search', + uri: '/api/pages/utils/search', method: METHODS.GET, mockResponse: SEARCH_PAGES, useAuthentication: true, @@ -221,7 +235,7 @@ describe('api/pages', () => { it('verify success groups', () => { getFreePages(); expect(makeRequest).toHaveBeenCalledWith({ - uri: '/api/pages/search/group/free', + uri: '/api/pages/utils/search/group/free', method: METHODS.GET, mockResponse: FREE_PAGES_PAYLOAD, useAuthentication: true, diff --git a/test/state/pages/actions.test.js b/test/state/pages/actions.test.js index 48ffdb091..ebae71ee0 100644 --- a/test/state/pages/actions.test.js +++ b/test/state/pages/actions.test.js @@ -54,6 +54,7 @@ jest.mock('state/pages/selectors', () => ({ getSelectedPage: jest.fn(), getReferencesFromSelectedPage: jest.fn(() => []), getAllPageTreeLoadedStatus: jest.fn(() => []), + getRootPageCode: jest.fn(() => 'homepage'), })); jest.mock('state/languages/selectors', () => ({ diff --git a/test/state/pages/selectors.test.js b/test/state/pages/selectors.test.js index 4a5d77951..f1492c73b 100644 --- a/test/state/pages/selectors.test.js +++ b/test/state/pages/selectors.test.js @@ -58,6 +58,7 @@ const MOCK_STATE = { freePages: [], selected: { ...HOMEPAGE_PAYLOAD, references: { jacmsContentManager: true } }, virtualRoot: true, + rootPage: 'homepage', }, }; diff --git a/test/ui/app/MfeContainer.test.js b/test/ui/app/MfeContainer.test.js index 5255a7434..9a6a6738a 100644 --- a/test/ui/app/MfeContainer.test.js +++ b/test/ui/app/MfeContainer.test.js @@ -38,6 +38,9 @@ describe('MfeContainer', () => { permissions: { loggedUser: [...mfeConfigMock.userPermissions], }, + pages: { + rootPage: 'homepage', + }, currentTenant: { currentTenant: {}, }, diff --git a/test/ui/pages/common/PageTreeContainer.test.js b/test/ui/pages/common/PageTreeContainer.test.js index 1490249e9..99537acb8 100644 --- a/test/ui/pages/common/PageTreeContainer.test.js +++ b/test/ui/pages/common/PageTreeContainer.test.js @@ -51,6 +51,7 @@ jest.mock('state/pages/selectors', () => ({ getPageTreePages: jest.fn(), getSearchPages: jest.fn(), getIsVirtualRootOn: jest.fn(), + getRootPageCode: jest.fn(), })); jest.mock('state/groups/selectors', () => ({