Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add external link option for IBN #2941

Merged
merged 20 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 47 additions & 19 deletions core/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -931,13 +931,13 @@
}
// insert modal into the modals list to be viewed on top of other modals
const newModal = {
mfModal : {
mfModal: {
displayed: true,
nodepath,
settings
}
settings,
},
};
mfModalList = [ ...mfModalList, newModal ];
mfModalList = [...mfModalList, newModal];

// check if modalPath feature enable and set URL accordingly
const showModalPathInUrl = LuigiConfig.getConfigBooleanValue(
Expand Down Expand Up @@ -1148,7 +1148,9 @@

if (params.fromVirtualTreeRoot) {
// from a parent node specified with virtualTree: true
const virtualTreeNode = [...localNavPath].reverse().find((n) => n.virtualTree);
const virtualTreeNode = [...localNavPath]
.reverse()
.find((n) => n.virtualTree);
if (!virtualTreeNode) {
console.error(
'LuigiClient Error: fromVirtualTreeRoot() is not possible because you are not inside a Luigi virtualTree navigation node.'
Expand Down Expand Up @@ -1178,7 +1180,7 @@
path = currentNodeViewUrl.split(navContextNodeViewUrl).join('');
} else {
// retrieve path for getCurrentPath method when no options used
path = currentNodeViewUrl;
path = currentNodeViewUrl;
}
return path;
};
Expand Down Expand Up @@ -1240,7 +1242,7 @@

EventListenerHelpers.addEventListener('message', async (e) => {
const iframe = IframeHelpers.getValidMessageSource(e);
const topMostModal = mfModalList[(mfModalList.length - 1)];
const topMostModal = mfModalList[mfModalList.length - 1];
const modalIframe = topMostModal && topMostModal.modalIframe;
const modalIframeData = topMostModal && topMostModal.modalIframeData;

Expand All @@ -1264,16 +1266,18 @@
const isSpecialIframe =
specialIframeMessageSource && specialIframeMessageSource.length > 0;

const skipInactiveConfig = LuigiConfig.getConfigValue('communication.skipEventsWhenInactive');
const skipInactiveConfig = LuigiConfig.getConfigValue(
'communication.skipEventsWhenInactive'
);

if(
if (
skipInactiveConfig &&
skipInactiveConfig.length > 0 &&
!isSpecialIframe &&
iframe.contentWindow !== window &&
!isSpecialIframe &&
iframe.contentWindow !== window &&
!GenericHelpers.isElementVisible(iframe) &&
skipInactiveConfig.includes(e.data.msg)
) {
) {
console.debug(`EVENT '${e.data.msg}' from inactive iframe -> SKIPPED`);
return;
}
Expand Down Expand Up @@ -1415,7 +1419,7 @@
const params = e.data.params;
const { intent, newTab, modal, splitView, drawer, withoutSync } =
params;
const isSpecial = newTab || modal || splitView || drawer;
let isSpecial = newTab || modal || splitView || drawer;

const resolveRemotePromise = () => {
const remotePromise = GenericHelpers.getRemotePromise(
Expand Down Expand Up @@ -1447,6 +1451,9 @@
params.link = params.link.split('?')[0];
}

let path = buildPath(e.data.params, srcNode, srcPathParams);
isSpecial = isSpecial || (intent && path.external);

if (!isSpecial) {
getUnsavedChangesModalPromise()
.then(() => {
Expand All @@ -1472,9 +1479,16 @@
rejectRemotePromise();
});
} else {
let path = buildPath(e.data.params, srcNode, srcPathParams);
path = GenericHelpers.addLeadingSlash(path);
// navigate to external link if external intent link detected
if (intent && path.external) {
Routing.navigateToExternalLink({
url: path.url,
sameWindow: !path.openInNewTab,
});
return;
}

path = GenericHelpers.addLeadingSlash(path);
if (newTab) {
await openViewInNewTab(path);
checkResolve();
Expand Down Expand Up @@ -1513,8 +1527,10 @@

if ('luigi.navigation.back' === e.data.msg) {
const mfModalTopMostElement = mfModalList[mfModalList.length - 1];

if (IframeHelpers.isMessageSource(e, mfModalTopMostElement && mfModalTopMostElement.modalIframe)) {
closeModal(mfModalList.length - 1, true);

await sendContextToClient(config, {
goBackContext:
e.data.goBackContext && JSON.parse(e.data.goBackContext),
Expand Down Expand Up @@ -1587,10 +1603,22 @@

if ('luigi.navigation.updateModalDataPath' === e.data.msg) {
if (isSpecialIframe) {
const route = GenericHelpers.addLeadingSlash(buildPath(e.data.params, iframe.luigi.currentNode, iframe.luigi.pathParams));
Routing.updateModalDataInUrl(route, e.data.params.modal, e.data.params.history);
const route = GenericHelpers.addLeadingSlash(
buildPath(
e.data.params,
iframe.luigi.currentNode,
iframe.luigi.pathParams
)
);
Routing.updateModalDataInUrl(
route,
e.data.params.modal,
e.data.params.history
);
} else {
console.warn('updateModalDataPath can only be called from modal, ignoring.');
console.warn(
'updateModalDataPath can only be called from modal, ignoring.'
);
}
}

Expand Down Expand Up @@ -1779,7 +1807,7 @@
};

export const hasBack = () => {
return (mfModalList.length > 0) || preservedViews.length !== 0;
return mfModalList.length > 0 || preservedViews.length !== 0;
};

onMount(() => {
Expand Down
7 changes: 4 additions & 3 deletions core/src/navigation/services/context-switcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ export const ContextSwitcherHelpers = {

return Boolean(
parentNodePath &&
currentPathNormalized &&
currentPathNormalized.startsWith(parentNodePathNormalized) &&
currentPathNormalized !== parentNodePathNormalized
currentPathNormalized &&
typeof currentPathNormalized === 'string' &&
currentPathNormalized.startsWith(parentNodePathNormalized) &&
currentPathNormalized !== parentNodePathNormalized
);
},

Expand Down
10 changes: 9 additions & 1 deletion core/src/services/routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class RoutingClass {
*/
setFeatureToggle(path) {
const featureToggleProperty = LuigiConfig.getConfigValue('settings.featureToggles.queryStringParam');
featureToggleProperty && RoutingHelpers.setFeatureToggles(featureToggleProperty, path);
featureToggleProperty && typeof path === 'string' && RoutingHelpers.setFeatureToggles(featureToggleProperty, path);
}

/**
Expand Down Expand Up @@ -321,6 +321,14 @@ class RoutingClass {
* @param {boolean} preventContextUpdate make no context update being triggered. default is false.
*/
async handleRouteChange(path, component, iframeElement, config, withoutSync, preventContextUpdate = false) {
// Handle intent navigation with new tab scenario.
if (path.external) {
this.navigateToExternalLink({
url: path.url,
sameWindow: !path.openInNewTab
});
return;
}
this.setFeatureToggle(path);
if (this.shouldSkipRoutingForUrlPatterns()) return;

Expand Down
8 changes: 8 additions & 0 deletions core/src/utilities/helpers/routing-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,15 @@ class RoutingHelpersClass {
if (!realPath) {
return false;
}
// set 'external' boolean to make it easier to identify new tab links
ndricimrr marked this conversation as resolved.
Show resolved Hide resolved
if (realPath.externalLink) {
return {
...realPath.externalLink,
external: true
}
}
realPath = realPath.pathSegment;

const params = Object.entries(intentObject.params);
if (params && params.length > 0) {
// resolve dynamic parameters in the path if any
Expand Down
51 changes: 51 additions & 0 deletions core/test/services/routing.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ describe('Routing', function() {
semanticObject: 'Sales',
action: 'settings',
pathSegment: '/projects/pr2/settings'
},
{
semanticObject: 'External',
action: 'view',
externalLink: { url: 'https://www.sap.com', openInNewTab: true }
},
{
semanticObject: 'External',
action: 'view2',
externalLink: { url: 'https://www.sap.com', openInNewTab: false }
}
]);
});
Expand Down Expand Up @@ -195,6 +205,26 @@ describe('Routing', function() {
const expected = '/projects/pr2/settings?~param1=hello&~param2=world';
assert.equal(actual, expected);
});

it('returns expected object for external intent links with openInNewTab true', () => {
const actual = RoutingHelpers.getIntentPath('#?intent=External-view');
const expected = {
url: 'https://www.sap.com',
openInNewTab: true,
external: true
};
assert.deepEqual(actual, expected);
});

it('returns expected object for external intent links with openInNewTab false', () => {
const actual = RoutingHelpers.getIntentPath('#?intent=External-view2');
const expected = {
url: 'https://www.sap.com',
openInNewTab: false,
external: true
};
assert.deepEqual(actual, expected);
});
});

describe('resolveDynamicIntentPath()', () => {
Expand Down Expand Up @@ -738,6 +768,27 @@ describe('Routing', function() {
sinon.assert.calledOnce(Iframe.switchActiveIframe);
sinon.assert.calledOnce(Routing.navigateWebComponent);
});

it('should call navigateToExternalLink if intent external link defined', async () => {
// given
const path = {
external: true,
url: 'https://www.test.com',
openInNewTab: true
};
const expectedParam = {
url: 'https://www.test.com',
sameWindow: false
};

sinon.stub(Routing, 'navigateToExternalLink');

// when
await Routing.handleRouteChange(path);

// then
sinon.assert.calledWithExactly(Routing.navigateToExternalLink, expectedParam);
});
});

describe('handleRouteClick', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class Navigation {
semanticObject: 'Component',
action: 'settings',
pathSegment: '/projects/:project/settings'
},
{
semanticObject: 'External',
action: 'view',
externalLink: { url: 'https://www.sap.com', openInNewTab: true }
}
];
nodeAccessibilityResolver = navigationPermissionChecker;
Expand Down