diff --git a/.github/workflows/publication.yml b/.github/workflows/publication.yml index 987323713..588bfd1fd 100644 --- a/.github/workflows/publication.yml +++ b/.github/workflows/publication.yml @@ -41,7 +41,7 @@ jobs: run: | ~/ppl-run \ .. checkout-branch --id "CHECKOUT FOR NEXUS PUBLICATION" \ - --lcd "$LOCAL_CLONE_DIR" \ + --lcd "$LOCAL_CLONE_DIR" \ --token "$ENTANDO_BOT_TOKEN" \ .. pr-preflight-checks --only flags --lcd "$LOCAL_CLONE_DIR" \ ; diff --git a/config/env.js b/config/env.js index 4812d5ee8..93e8648ef 100644 --- a/config/env.js +++ b/config/env.js @@ -61,6 +61,10 @@ const RUNTIME_OVERRIDABLE_VARS = [ 'COMPONENT_REPOSITORY_UI_ENABLED', 'DOMAIN', 'KEYCLOAK_JSON', + 'WEBUI_ENABLED', + 'WEBUI_APP_MANAGEMENT_URL', + 'WEBUI_APP_URL', + 'WEBUI_DEV_WORKSPACE_URL', ]; function getClientEnvironment(publicUrl) { diff --git a/src/api/pages.js b/src/api/pages.js index bd3d3246f..0991a3333 100644 --- a/src/api/pages.js +++ b/src/api/pages.js @@ -12,6 +12,11 @@ import { import { PAGE_STATUS_DRAFT } from 'state/pages/const'; +import getRuntimeEnv from 'helpers/getRuntimeEnv'; + +const { WEBUI_APP_MANAGEMENT_URL } = getRuntimeEnv(); + + /* * - homepage * |- dashboard @@ -285,3 +290,57 @@ export const postClonePage = (pageCode, pageObject) => makeRequest({ mockResponse: {}, useAuthentication: true, }); + +export const postWebuiPage = pageObject => makeRequest({ + uri: '/api/pages', + method: METHODS.POST, + body: pageObject, + mockResponse: {}, + domain: WEBUI_APP_MANAGEMENT_URL, + useAuthentication: true, +}); + +export const postWebuiClonePage = (pageCode, pageObject) => makeRequest({ + uri: `/api/pages/${pageCode}/clone`, + method: METHODS.POST, + body: pageObject, + mockResponse: {}, + domain: WEBUI_APP_MANAGEMENT_URL, + useAuthentication: true, +}); + +export const putWebuiPage = pageObject => makeRequest({ + uri: `/api/pages/${pageObject.code}`, + body: pageObject, + method: METHODS.PUT, + mockResponse: { ...pageObject }, + domain: WEBUI_APP_MANAGEMENT_URL, + useAuthentication: true, + errors: () => ( + fetchPageResponseMap[pageObject.code] ? + [] : + [{ code: 1, message: `no page with the code ${pageObject.code} could be found.` }] + ), +}); + +export const putWebuiPageStatus = (pageCode, status) => makeRequest({ + uri: `/api/pages/${pageCode}/status`, + body: { status }, + method: METHODS.PUT, + mockResponse: { ...fetchPageResponseMap.homepage, status }, + useAuthentication: true, + domain: WEBUI_APP_MANAGEMENT_URL, + errors: () => ( + fetchPageResponseMap[pageCode] ? + [] : + [{ code: 1, message: `no page with the code ${pageCode} could be found.` }] + ), +}); + +export const deleteWebuiPage = page => makeRequest({ + uri: `/api/pages/${page.code}`, + method: METHODS.DELETE, + mockResponse: { code: `${page.code}` }, + domain: WEBUI_APP_MANAGEMENT_URL, + useAuthentication: true, +}); diff --git a/src/helpers/getRuntimeEnv.js b/src/helpers/getRuntimeEnv.js index 7ef5029b2..42331f1a9 100644 --- a/src/helpers/getRuntimeEnv.js +++ b/src/helpers/getRuntimeEnv.js @@ -33,4 +33,8 @@ export default () => ({ COMPONENT_REPOSITORY_UI_ENABLED: getBooleanEnvVar('COMPONENT_REPOSITORY_UI_ENABLED'), DOMAIN: validateDomain(getEnvVar('DOMAIN')), KEYCLOAK_JSON: getEnvVar('KEYCLOAK_JSON') || `${validateDomain(getEnvVar('DOMAIN'))}/keycloak.json`, + WEBUI_ENABLED: getBooleanEnvVar('WEBUI_ENABLED'), + WEBUI_APP_MANAGEMENT_URL: getEnvVar('WEBUI_APP_MANAGEMENT_URL'), + WEBUI_APP_URL: getEnvVar('WEBUI_APP_URL'), + WEBUI_DEV_WORKSPACE_URL: getEnvVar('WEBUI_DEV_WORKSPACE_URL'), }); diff --git a/src/helpers/urlUtils.js b/src/helpers/urlUtils.js index bc59a3dfd..3497fa373 100644 --- a/src/helpers/urlUtils.js +++ b/src/helpers/urlUtils.js @@ -1,3 +1,8 @@ import { get } from 'lodash'; -// eslint-disable-next-line import/prefer-default-export -export const adminConsoleUrl = url => `${get(process.env, 'DOMAIN', '')}/${url}`; + +export const adminConsoleUrl = url => `${get(process.env, 'REACT_APP_DOMAIN', '')}/${url}`; + +export const openInNewTab = (url) => { + const newWindow = window.open(url, '_blank', 'noopener,noreferrer'); + if (newWindow) newWindow.opener = null; +}; diff --git a/src/state/pages/actions.js b/src/state/pages/actions.js index 6a11b0e95..6930a4e5f 100644 --- a/src/state/pages/actions.js +++ b/src/state/pages/actions.js @@ -6,6 +6,7 @@ import { getPage, getPageChildren, setPagePosition, postPage, deletePage, getFreePages, getPageSettings, putPage, putPageStatus, getViewPages, getSearchPages, putPageSettings, patchPage, getPageSEO, postPageSEO, putPageSEO, postClonePage, + deleteWebuiPage, postWebuiPage, putWebuiPageStatus, postWebuiClonePage, putWebuiPage, } from 'api/pages'; import { getStatusMap, @@ -33,6 +34,10 @@ import { APP_TOUR_CANCELLED, APP_TOUR_STARTED, APP_TOUR_HOMEPAGE_CODEREF } from import { setExistingPages } from 'state/app-tour/actions'; import { getAppTourProgress } from 'state/app-tour/selectors'; +import getRuntimeEnv from 'helpers/getRuntimeEnv'; + +const { WEBUI_ENABLED } = getRuntimeEnv(); + const RESET_FOR_CLONE = { code: '', titles: '', @@ -197,16 +202,23 @@ export const fetchViewPages = () => dispatch => new Promise((resolve) => { export const sendDeletePage = (page, successRedirect = true) => async (dispatch) => { try { - const response = await deletePage(page); + let response = null; + if (WEBUI_ENABLED) { + response = await deleteWebuiPage(page); + } else { + response = await deletePage(page); + } const json = await response.json(); if (response) { dispatch(removePage(page)); + dispatch(addToast({ id: 'app.deleted', values: { type: 'page', code: page.code } }, TOAST_SUCCESS)); if (page.tourProgress === APP_TOUR_CANCELLED) return; if (page.tourProgress !== APP_TOUR_STARTED && successRedirect) { history.push(ROUTE_PAGE_TREE); } } else { dispatch(addErrors(json.errors.map(e => e.message))); + json.errors.forEach(err => dispatch(addToast(err.message, TOAST_ERROR))); } } catch (e) { // do nothing @@ -316,11 +328,20 @@ export const sendPostPage = pageData => dispatch => new Promise(async (resolve) }, } : {}; const postPageCall = SEO_ENABLED ? postPageSEO : postPage; - const response = await postPageCall({ - ...pageData, - ...seoPayload, - }); - const json = await response.json(); + let response = { json: () => {} }; + let json = { errors: [] }; + if (WEBUI_ENABLED) { + response = await postWebuiPage({ + ...pageData, + ...seoPayload, + }); + } else { + response = await postPageCall({ + ...pageData, + ...seoPayload, + }); + } + json = await response.json(); if (response.ok) { dispatch(addToast({ id: 'pages.created' }, TOAST_SUCCESS)); dispatch(addPages([json.payload])); @@ -350,7 +371,9 @@ export const sendClonePage = (pageCode, pageData) => dispatch => new Promise(asy titles, }; - const response = await postClonePage(pageCode, requestBody); + const cloneApi = WEBUI_ENABLED ? postWebuiClonePage : postClonePage; + + const response = await cloneApi(pageCode, requestBody); const json = await response.json(); if (response.ok) { @@ -453,7 +476,8 @@ export const sendPutPage = pageData => dispatch => useExtraTitles: seo, }, } : {}; - const response = await putPageFunc({ + const putApiCall = WEBUI_ENABLED ? putWebuiPage : putPageFunc; + const response = await putApiCall({ ...pageData, ...seoPayload, }); @@ -539,7 +563,9 @@ const putSelectedPageStatus = status => (dispatch, getState) => status: status === PAGE_STATUS_DRAFT ? PAGE_STATUS_UNPUBLISHED : status, }; dispatch(setPageLoading(page.code)); - putPageStatus(page.code, status).then((response) => { + const pageStatusApiCall = WEBUI_ENABLED ? + putWebuiPageStatus : putPageStatus; + pageStatusApiCall(page.code, status).then((response) => { if (response.ok) { dispatch(setSelectedPage(newPage)); dispatch(updatePage(newPage)); diff --git a/src/ui/assets/AssetsList.js b/src/ui/assets/AssetsList.js index 7da2936f2..cc168b4f4 100644 --- a/src/ui/assets/AssetsList.js +++ b/src/ui/assets/AssetsList.js @@ -27,6 +27,7 @@ import { SORTABLE_COLUMNS, ASSET_FILETYPES, } from 'state/assets/const'; +import { openInNewTab } from 'helpers/urlUtils'; class AssetsList extends Component { constructor(props) { @@ -281,7 +282,7 @@ class AssetsList extends Component { onDuplicateClicked(asset)}> - window.open(asset.downloadUrl)}> + openInNewTab(asset.downloadUrl)}> onClickDelete(asset)}> diff --git a/src/ui/assets/AssetsListGridView.js b/src/ui/assets/AssetsListGridView.js index 64f4a2137..92e498ce3 100644 --- a/src/ui/assets/AssetsListGridView.js +++ b/src/ui/assets/AssetsListGridView.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { DropdownKebab, MenuItem, Button } from 'patternfly-react'; +import { openInNewTab } from 'helpers/urlUtils'; const AssetsListGridView = ({ assets, @@ -25,7 +26,7 @@ const AssetsListGridView = ({ const onEditClickHandle = () => onEditClicked(asset); const onClickDeleteHandle = () => onClickDelete(asset); const onDuplicateClickHandle = () => onDuplicateClicked(asset); - const onDownloadHandle = () => window.open(asset.downloadUrl); + const onDownloadHandle = () => openInNewTab(asset.downloadUrl); const onClickSelectHandle = () => onItemSelected(asset); return (
diff --git a/src/ui/page-templates/list/PageTemplateListMenuActions.js b/src/ui/page-templates/list/PageTemplateListMenuActions.js index 25252d356..4e38d2d86 100644 --- a/src/ui/page-templates/list/PageTemplateListMenuActions.js +++ b/src/ui/page-templates/list/PageTemplateListMenuActions.js @@ -11,8 +11,7 @@ import { ROUTE_PAGE_TEMPLATE_DETAIL, } from 'app-init/router'; - -const PageTemplateListMenuActions = ({ onClickDelete, code }) => ( +const PageTemplateListMenuActions = ({ onClickDelete, code, locked }) => ( ( > - - - + { + !locked && ( + + + + ) + } ); PageTemplateListMenuActions.propTypes = { onClickDelete: PropTypes.func, code: PropTypes.string.isRequired, + locked: PropTypes.bool.isRequired, }; PageTemplateListMenuActions.defaultProps = { diff --git a/src/ui/page-templates/list/PageTemplateListTable.js b/src/ui/page-templates/list/PageTemplateListTable.js index 3cc14c209..c1ab470f5 100644 --- a/src/ui/page-templates/list/PageTemplateListTable.js +++ b/src/ui/page-templates/list/PageTemplateListTable.js @@ -81,6 +81,7 @@ class PageTemplateListTable extends Component { Cell: ({ values }) => ( removePageTemplate(values.code)} /> ), diff --git a/src/ui/pages/add/PagesAddFormContainer.js b/src/ui/pages/add/PagesAddFormContainer.js index b459e8ccb..a290cf112 100644 --- a/src/ui/pages/add/PagesAddFormContainer.js +++ b/src/ui/pages/add/PagesAddFormContainer.js @@ -10,7 +10,7 @@ import { getPageTemplatesList } from 'state/page-templates/selectors'; import { getCharsets, getContentTypes, getSelectedPageLocaleTitle } from 'state/pages/selectors'; import { sendPostPage, loadSelectedPage } from 'state/pages/actions'; import { history, ROUTE_PAGE_TREE, ROUTE_PAGE_CONFIG } from 'app-init/router'; -import { PAGE_INIT_VALUES, SEO_DATA_BLANK, SEO_LANGDATA_BLANK } from 'ui/pages/common/const'; +import { NEXT_PAGE_TEMPLATE, PAGE_INIT_VALUES, SEO_DATA_BLANK, SEO_LANGDATA_BLANK } from 'ui/pages/common/const'; import { getLocale } from 'state/locale/selectors'; import getSearchParam from 'helpers/getSearchParam'; import { setVisibleModal } from 'state/modal/actions'; @@ -23,6 +23,10 @@ import { getMyGroupPermissions } from 'state/permissions/selectors'; import { fetchMyGroupPermissions } from 'state/permissions/actions'; import { fetchAllGroupEntries, fetchMyGroups } from 'state/groups/actions'; import { getGroupEntries, getGroupsList } from 'state/groups/selectors'; +import getRuntimeEnv from 'helpers/getRuntimeEnv'; +import { openInNewTab } from 'helpers/urlUtils'; + +const { WEBUI_ENABLED, WEBUI_DEV_WORKSPACE_URL } = getRuntimeEnv(); export const getDefaultLanguage = (languages) => { const defaultLang = { code: 'en' }; @@ -194,7 +198,12 @@ export const mapDispatchToProps = dispatch => ({ dispatch(setAppTourLastStep(12)); dispatch(setTourCreatedPage(data)); } - history.push(routeConverter(ROUTE_PAGE_CONFIG, { pageCode: data.code })); + if (WEBUI_ENABLED && data.pageModel === NEXT_PAGE_TEMPLATE) { + openInNewTab(WEBUI_DEV_WORKSPACE_URL); + history.push(ROUTE_PAGE_TREE); + } else { + history.push(routeConverter(ROUTE_PAGE_CONFIG, { pageCode: data.code })); + } break; } default: history.push(ROUTE_PAGE_TREE); diff --git a/src/ui/pages/clone/CloneFormContainer.js b/src/ui/pages/clone/CloneFormContainer.js index c9235bb77..61dd13951 100644 --- a/src/ui/pages/clone/CloneFormContainer.js +++ b/src/ui/pages/clone/CloneFormContainer.js @@ -13,6 +13,11 @@ import { sendClonePage } from 'state/pages/actions'; import { history, ROUTE_PAGE_TREE, ROUTE_PAGE_CONFIG } from 'app-init/router'; import { setVisibleModal } from 'state/modal/actions'; import getSearchParam from 'helpers/getSearchParam'; +import getRuntimeEnv from 'helpers/getRuntimeEnv'; +import { openInNewTab } from 'helpers/urlUtils'; +import { NEXT_PAGE_TEMPLATE } from 'ui/pages/common/const'; + +const { WEBUI_ENABLED, WEBUI_DEV_WORKSPACE_URL } = getRuntimeEnv(); export const mapStateToProps = state => ({ languages: getActiveLanguages(state), @@ -47,7 +52,12 @@ export const mapDispatchToProps = dispatch => ({ break; } case ACTION_SAVE_AND_CONFIGURE: { - history.push(routeConverter(ROUTE_PAGE_CONFIG, { pageCode: data.code })); + if (WEBUI_ENABLED && data.pageModel === NEXT_PAGE_TEMPLATE) { + openInNewTab(WEBUI_DEV_WORKSPACE_URL); + history.push(ROUTE_PAGE_TREE); + } else { + history.push(routeConverter(ROUTE_PAGE_CONFIG, { pageCode: data.code })); + } break; } default: history.push(ROUTE_PAGE_TREE); diff --git a/src/ui/pages/common/FindTemplateModal.js b/src/ui/pages/common/FindTemplateModal.js index 45165628b..4bf9c42b1 100644 --- a/src/ui/pages/common/FindTemplateModal.js +++ b/src/ui/pages/common/FindTemplateModal.js @@ -6,6 +6,7 @@ import { Modal, Col, Row, Spinner, DropdownKebab, MenuItem, Button } from 'patte import GenericModalContainer from 'ui/common/modal/GenericModalContainer'; import { ROUTE_PAGE_TEMPLATE_DETAIL } from 'app-init/router'; import { TEMPLATE_THUMBNAIL } from 'ui/pages/common/const'; +import { openInNewTab } from 'helpers/urlUtils'; export const MODAL_ID = 'FindTemplateModal'; @@ -59,7 +60,7 @@ const FindTemplateModal = ({ onClick={() => { const templatePath = ROUTE_PAGE_TEMPLATE_DETAIL.replace(':pageTemplateCode', code); const link = `${global.location.href.split('/page')[0]}${templatePath}`; - window.open(link); + openInNewTab(link); }} > diff --git a/src/ui/pages/common/PageTree.js b/src/ui/pages/common/PageTree.js index a995493c2..fc5dc3f45 100644 --- a/src/ui/pages/common/PageTree.js +++ b/src/ui/pages/common/PageTree.js @@ -17,6 +17,10 @@ import UnpublishPageModalContainer from 'ui/pages/common/UnpublishPageModalConta import PageListSearchTable from 'ui/pages/list/PageListSearchTable'; import MovePageModalContainer from 'ui/pages/common/MovePageModalContainer'; import { PAGE_MOVEMENT_OPTIONS } from 'state/pages/const'; +import { NEXT_PAGE_TYPE } from 'ui/pages/common/const'; +import getRuntimeEnv from 'helpers/getRuntimeEnv'; + +const { WEBUI_ENABLED } = getRuntimeEnv(); class PageTree extends Component { constructor(props) { @@ -28,7 +32,8 @@ class PageTree extends Component { componentDidMount() { const { columnOrder, onSetColumnOrder } = this.props; if (!columnOrder.length) { - onSetColumnOrder(['title', 'status', 'displayedInMenu']); + onSetColumnOrder(WEBUI_ENABLED ? ['title', 'type', 'status', 'displayedInMenu'] : + ['title', 'status', 'displayedInMenu']); } } @@ -103,6 +108,22 @@ class PageTree extends Component { return { className: className.join(' ') }; }, }, + ...(WEBUI_ENABLED && { + type: { + Header: , + attributes: { + className: 'text-center PageTree__thead', + style: { width: '5%', verticalAlign: 'middle' }, + }, + Cell: ({ value }) => ( + + ), + cellAttributes: { + className: 'text-center', + style: { verticalAlign: 'middle' }, + }, + }, + }), status: { Header: , attributes: { @@ -229,6 +250,7 @@ PageTree.propTypes = { code: PropTypes.string.isRequired, status: PropTypes.string.isRequired, displayedInMenu: PropTypes.bool.isRequired, + type: PropTypes.string.isRequired, title: PropTypes.string.isRequired, depth: PropTypes.number.isRequired, expanded: PropTypes.bool.isRequired, @@ -266,7 +288,8 @@ PageTree.defaultProps = { onExpandAll: () => {}, onCollapseAll: () => {}, onSetColumnOrder: () => {}, - columnOrder: ['title', 'status', 'displayedInMenu'], + columnOrder: WEBUI_ENABLED ? ['title', 'type', 'status', 'displayedInMenu'] : + ['title', 'status', 'displayedInMenu'], }; export default PageTree; diff --git a/src/ui/pages/common/PageTreeActionMenu.js b/src/ui/pages/common/PageTreeActionMenu.js index b79a84003..5e5aeacc7 100644 --- a/src/ui/pages/common/PageTreeActionMenu.js +++ b/src/ui/pages/common/PageTreeActionMenu.js @@ -3,6 +3,11 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { DropdownKebab, MenuItem } from 'patternfly-react'; import { PAGE_STATUS_PUBLISHED, PAGE_STATUS_UNPUBLISHED } from 'state/pages/const'; +import { NEXT_PAGE_TYPE } from 'ui/pages/common/const'; +import getRuntimeEnv from 'helpers/getRuntimeEnv'; +import { openInNewTab } from 'helpers/urlUtils'; + +const { WEBUI_APP_URL, WEBUI_DEV_WORKSPACE_URL } = getRuntimeEnv(); class PageTreeActionMenu extends Component { constructor(props) { @@ -89,6 +94,8 @@ class PageTreeActionMenu extends Component { ); + const handleDesignNextPage = () => openInNewTab(WEBUI_DEV_WORKSPACE_URL); + const handlePreviewNextPage = () => openInNewTab(`${WEBUI_APP_URL}/${this.props.locale}/${page.code}.page`); return (
e.stopPropagation()} role="none" data-testid={`${page.code}-actions`}> @@ -109,7 +116,8 @@ class PageTreeActionMenu extends Component { {onClickConfigure && ( @@ -130,12 +138,16 @@ class PageTreeActionMenu extends Component { )} {renderDeleteItem()} - - - + { + + + + + } {viewPublishedPage}
@@ -151,6 +163,7 @@ PageTreeActionMenu.propTypes = { hasPublishedChildren: PropTypes.bool, code: PropTypes.string, parentStatus: PropTypes.string, + type: PropTypes.string, }).isRequired, onClickAdd: PropTypes.func, onClickEdit: PropTypes.func, diff --git a/src/ui/pages/common/PageTreeContainer.js b/src/ui/pages/common/PageTreeContainer.js index 7e78203b3..37a026647 100644 --- a/src/ui/pages/common/PageTreeContainer.js +++ b/src/ui/pages/common/PageTreeContainer.js @@ -34,6 +34,7 @@ import { PAGE_INIT_VALUES } from 'ui/pages/common/const'; import { setAppTourLastStep } from 'state/app-tour/actions'; import { getDomain } from '@entando/apimanager'; import { PREVIEW_NAMESPACE } from 'ui/pages/config/const'; +import { openInNewTab } from 'helpers/urlUtils'; export const mapStateToProps = state => ({ locale: getLocale(state), @@ -89,10 +90,10 @@ export const mapDispatchToProps = (dispatch, ownProps) => ({ dispatch(clearSearchPage()); }, onClickViewPublishedPage: (page, domain, locale) => { - window.open(`${domain}/${locale}/${page.code}.page`, '_blank'); + openInNewTab(`${domain}/${locale}/${page.code}.page`); }, onClickPreview: (page, domain) => { - window.open(`${domain}/${PREVIEW_NAMESPACE}?pageCode=${page.code}&token=${page.token}`, '_blank'); + openInNewTab(`${domain}/${PREVIEW_NAMESPACE}?pageCode=${page.code}&token=${page.token}`); }, onDropPage: (sourcePageCode, targetPageCode, action) => { dispatch(setVisibleModal(MOVE_MODAL_ID)); diff --git a/src/ui/pages/common/const.js b/src/ui/pages/common/const.js index c5c0e8201..2181fc2b2 100644 --- a/src/ui/pages/common/const.js +++ b/src/ui/pages/common/const.js @@ -44,3 +44,6 @@ export const TEMPLATE_THUMBNAIL = { '1-column': `${publicUrl}/images/1-column.svg`, custom: `${publicUrl}/images/custom-template.svg`, }; + +export const NEXT_PAGE_TYPE = 'NX'; +export const NEXT_PAGE_TEMPLATE = 'nx-page-template'; diff --git a/src/ui/pages/config/ContentPagesContainer.js b/src/ui/pages/config/ContentPagesContainer.js index 71a4b1e2b..b5ebbe265 100644 --- a/src/ui/pages/config/ContentPagesContainer.js +++ b/src/ui/pages/config/ContentPagesContainer.js @@ -26,6 +26,7 @@ import { getUserPreferences } from 'state/user-preferences/selectors'; import { history, ROUTE_PAGE_CONFIG } from 'app-init/router'; import { getDomain } from '@entando/apimanager'; import { PREVIEW_NAMESPACE } from 'ui/pages/config/const'; +import { openInNewTab } from 'helpers/urlUtils'; export const mapStateToProps = state => ({ loading: getLoading(state).pageTree, @@ -72,10 +73,10 @@ export const mapDispatchToProps = dispatch => ({ dispatch(clearSearchPage()); }, onClickPreview: (page, domain) => { - window.open(`${domain}/${PREVIEW_NAMESPACE}?pageCode=${page.code}&token=${page.token}`, '_blank'); + openInNewTab(`${domain}/${PREVIEW_NAMESPACE}?pageCode=${page.code}&token=${page.token}`); }, onClickViewPublishedPage: (page, domain, locale) => { - window.open(`${domain}/${locale}/${page.code}.page`, '_blank'); + openInNewTab(`${domain}/${locale}/${page.code}.page`); }, onExpandAll: (currentPage) => { dispatch(fetchPageTreeAll()); diff --git a/src/ui/pages/config/PageConfigPage.js b/src/ui/pages/config/PageConfigPage.js index 9dfa42b5c..85b7aae44 100644 --- a/src/ui/pages/config/PageConfigPage.js +++ b/src/ui/pages/config/PageConfigPage.js @@ -15,6 +15,7 @@ import AppTourContainer from 'ui/app-tour/AppTourContainer'; import { APP_TOUR_STARTED } from 'state/app-tour/const'; import { PAGE_STATUS_PUBLISHED, PAGE_STATUS_UNPUBLISHED } from 'state/pages/const'; import PagesEditFormContainer from 'ui/pages/edit/PagesEditFormContainer'; +import { openInNewTab } from 'helpers/urlUtils'; const msgs = defineMessages({ appYes: { @@ -107,7 +108,7 @@ class PageConfigPage extends Component { openLinkPublishedPage() { if (this.props.pageStatus !== PAGE_STATUS_UNPUBLISHED) { - window.open(this.props.publishedPageUri, '_blank'); + openInNewTab(this.props.publishedPageUri); } } diff --git a/src/ui/pages/edit/PagesEditFormContainer.js b/src/ui/pages/edit/PagesEditFormContainer.js index a5cf3104e..058c0eadf 100644 --- a/src/ui/pages/edit/PagesEditFormContainer.js +++ b/src/ui/pages/edit/PagesEditFormContainer.js @@ -14,6 +14,11 @@ import { fetchPageTemplates } from 'state/page-templates/actions'; import { history, ROUTE_PAGE_TREE, ROUTE_PAGE_CONFIG } from 'app-init/router'; import { fetchLanguages } from 'state/languages/actions'; import { setVisibleModal } from 'state/modal/actions'; +import getRuntimeEnv from 'helpers/getRuntimeEnv'; +import { openInNewTab } from 'helpers/urlUtils'; +import { NEXT_PAGE_TEMPLATE } from 'ui/pages/common/const'; + +const { WEBUI_ENABLED, WEBUI_DEV_WORKSPACE_URL } = getRuntimeEnv(); export const FORM_ID = 'pageEdit'; @@ -44,7 +49,12 @@ export const mapDispatchToProps = (dispatch, ownProps) => ({ break; } case ACTION_SAVE_AND_CONFIGURE: { - history.push(routeConverter(ROUTE_PAGE_CONFIG, { pageCode: data.code })); + if (WEBUI_ENABLED && data.pageModel === NEXT_PAGE_TEMPLATE) { + openInNewTab(WEBUI_DEV_WORKSPACE_URL); + history.push(ROUTE_PAGE_TREE); + } else { + history.push(routeConverter(ROUTE_PAGE_CONFIG, { pageCode: data.code })); + } break; } default: history.push(ROUTE_PAGE_TREE); diff --git a/test/api/pages.test.js b/test/api/pages.test.js index 5397f4c84..a6010db77 100644 --- a/test/api/pages.test.js +++ b/test/api/pages.test.js @@ -2,7 +2,8 @@ import 'test/enzyme-init'; import { getPage, getPageChildren, setPagePosition, postPage, putPage, patchPage, deletePage, getSearchPages, getPageSettings, getFreePages, getPageConfig, deletePageWidget, putPageWidget, - getReferencesPage, restorePageConfig, applyDefaultPageConfig, putPageSettings, + getReferencesPage, restorePageConfig, applyDefaultPageConfig, putPageSettings, deleteWebuiPage, + postWebuiPage, putWebuiPageStatus, postWebuiClonePage, putWebuiPage, } from 'api/pages'; import { CONTACTS_PAYLOAD, FREE_PAGES_PAYLOAD, PAGE_SETTINGS_PAYLOAD, SEARCH_PAGES } from 'test/mocks/pages'; @@ -101,6 +102,38 @@ describe('api/pages', () => { }); }); + describe('postWebuiPage', () => { + it('returns a promise', () => { + expect(postWebuiPage(CONTACTS_PAYLOAD)).toBeInstanceOf(Promise); + }); + + it('makes the correct request', () => { + postWebuiPage(CONTACTS_PAYLOAD); + expect(makeRequest).toHaveBeenCalledWith(expect.objectContaining({ + uri: '/api/pages', + body: CONTACTS_PAYLOAD, + method: METHODS.POST, + useAuthentication: true, + })); + }); + }); + + describe('postWebuiClonePage', () => { + it('returns a promise', () => { + expect(postWebuiClonePage('contacts', CONTACTS_PAYLOAD)).toBeInstanceOf(Promise); + }); + + it('makes the correct request', () => { + postWebuiPage('contacts', CONTACTS_PAYLOAD); + expect(makeRequest).toHaveBeenCalledWith(expect.objectContaining({ + uri: '/api/pages/contacts/clone', + body: CONTACTS_PAYLOAD, + method: METHODS.POST, + useAuthentication: true, + })); + }); + }); + fdescribe('patchPage', () => { const jsonPatch = [ { @@ -160,6 +193,22 @@ describe('api/pages', () => { }); }); + describe('deleteWebuiPage', () => { + it('returns a promise', () => { + expect(deleteWebuiPage(CONTACTS_PAYLOAD)).toBeInstanceOf(Promise); + }); + + it('makes the correct request', () => { + deleteWebuiPage(CONTACTS_PAYLOAD); + expect(makeRequest).toHaveBeenCalledWith(expect.objectContaining({ + uri: `/api/pages/${CONTACTS_PAYLOAD.code}`, + method: METHODS.DELETE, + mockResponse: { code: CONTACTS_PAYLOAD.code }, + useAuthentication: true, + })); + }); + }); + describe('getPageSettings', () => { it('returns a promise', () => { expect(getPageSettings()).toBeInstanceOf(Promise); @@ -326,4 +375,37 @@ describe('api/pages', () => { })); }); }); + + describe('putWebuiPageStatus', () => { + it('returns a promise', () => { + expect(putWebuiPageStatus(CONTACTS_PAYLOAD)).toBeInstanceOf(Promise); + }); + + it('makes the correct request', () => { + putWebuiPageStatus('code1', 'published'); + expect(makeRequest).toHaveBeenCalledWith(expect.objectContaining({ + uri: '/api/pages/code1/status', + body: { status: 'published' }, + method: METHODS.PUT, + useAuthentication: true, + })); + }); + }); + + describe('putWebuiPage', () => { + it('returns a promise', () => { + expect(putWebuiPage(CONTACTS_PAYLOAD)).toBeInstanceOf(Promise); + }); + + it('makes the correct request', () => { + putWebuiPage(CONTACTS_PAYLOAD); + expect(makeRequest).toHaveBeenCalledWith(expect.objectContaining({ + uri: `/api/pages/${CONTACTS_PAYLOAD.code}`, + body: CONTACTS_PAYLOAD, + method: METHODS.PUT, + errors: expect.any(Function), + useAuthentication: true, + })); + }); + }); }); diff --git a/test/test/mocks/pages.js b/test/test/mocks/pages.js index da426ceb1..bb75ca2af 100644 --- a/test/test/mocks/pages.js +++ b/test/test/mocks/pages.js @@ -87,6 +87,7 @@ export const SERVICE_PAYLOAD = { parentCode: 'homepage', seo: false, position: 3, + type: 'NX', titles: { it: 'Pagina di servizio', en: 'Service page',