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

Implement intent based navigation #1634

Merged
merged 42 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0f5caef
save current progress
ndricimrr Sep 15, 2020
d2ae588
save current progress - 2
ndricimrr Sep 16, 2020
e1f8cae
save current progress - 2
ndricimrr Sep 16, 2020
e6d2f69
major refactoring and comments
ndricimrr Sep 17, 2020
5997992
remove changes
ndricimrr Sep 17, 2020
4763dae
consistent fix for hash and path based
ndricimrr Sep 18, 2020
396201c
save current unit test progress
ndricimrr Sep 18, 2020
ba8b72d
add more tests
ndricimrr Sep 18, 2020
eacb942
add more unit tests
ndricimrr Sep 18, 2020
f21b3d7
add unit and e2e test
ndricimrr Sep 20, 2020
5d016b5
add documentation
ndricimrr Sep 21, 2020
2a33e13
fix docu and remove extra changes
ndricimrr Sep 21, 2020
e9b26bc
Merge branch 'master' into 1605-intent-based-navigation
ndricimrr Sep 21, 2020
7041a44
Merge branch 'master' into 1605-intent-based-navigation
ndricimrr Sep 22, 2020
b05db73
Merge branch 'master' into 1605-intent-based-navigation
JohannesDoberer Sep 30, 2020
9edd403
Update docs/advanced-scenarios.md
ndricimrr Oct 7, 2020
9f62f01
Update docs/advanced-scenarios.md
ndricimrr Oct 7, 2020
847a357
Update docs/advanced-scenarios.md
ndricimrr Oct 7, 2020
7518700
Update docs/advanced-scenarios.md
ndricimrr Oct 7, 2020
9b5417d
Update docs/advanced-scenarios.md
ndricimrr Oct 7, 2020
1fb64fb
Update docs/advanced-scenarios.md
ndricimrr Oct 7, 2020
bc0d0a0
Update docs/advanced-scenarios.md
ndricimrr Oct 7, 2020
18b140b
Update docs/navigation-parameters-reference.md
ndricimrr Oct 7, 2020
d98d6bc
Update docs/navigation-parameters-reference.md
ndricimrr Oct 7, 2020
fd5bddc
Update docs/navigation-parameters-reference.md
ndricimrr Oct 7, 2020
b1c4381
Update docs/navigation-parameters-reference.md
ndricimrr Oct 7, 2020
ee5b537
Update core/src/utilities/helpers/routing-helpers.js
ndricimrr Oct 7, 2020
56e6709
Update core/src/utilities/helpers/routing-helpers.js
ndricimrr Oct 7, 2020
a401ec1
Merge branch 'master' into 1605-intent-based-navigation
ndricimrr Oct 7, 2020
8154cf1
apply suggestions
ndricimrr Oct 8, 2020
09f35aa
apply suggestions 2
ndricimrr Oct 9, 2020
cf716bf
Update client/src/linkManager.js
ndricimrr Oct 14, 2020
c3b500b
fix typo
ndricimrr Oct 14, 2020
69b603e
add suggestion
ndricimrr Oct 14, 2020
68ad4d6
change variable name
ndricimrr Oct 14, 2020
197c26f
make case insensitive
ndricimrr Oct 15, 2020
0d80ba3
Merge branch '1605-intent-based-navigation' of github.com:ndricimrr/l…
ndricimrr Oct 15, 2020
56d25ae
Merge branch 'master' into 1605-intent-based-navigation
ndricimrr Oct 15, 2020
587ac2c
Merge branch 'master' into 1605-intent-based-navigation
stanleychh Oct 19, 2020
5616589
Merge branch 'master' into 1605-intent-based-navigation
UlianaMunich Oct 22, 2020
cd89d87
Merge branch 'master' into 1605-intent-based-navigation
ndricimrr Oct 28, 2020
bd31fb8
Merge branch 'master' into 1605-intent-based-navigation
ndricimrr Oct 28, 2020
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
1 change: 1 addition & 0 deletions client/luigi-client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export declare interface LinkManager {
* LuigiClient.linkManager().navigate('/overview')
* LuigiClient.linkManager().navigate('users/groups/stakeholders')
* LuigiClient.linkManager().navigate('/settings', null, true) // preserve view
* LuigiClient.linkManager().navigate('#?Intent=Sales-order?id=13') // intent navigation
*/
navigate: (
path: string,
Expand Down
3 changes: 3 additions & 0 deletions client/src/linkManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class linkManager extends LuigiClientBase {
* LuigiClient.linkManager().navigate('/overview')
* LuigiClient.linkManager().navigate('users/groups/stakeholders')
* LuigiClient.linkManager().navigate('/settings', null, true) // preserve view
* LuigiClient.linkManager().navigate('#Sales-order?id=13') // intent navigation
ndricimrr marked this conversation as resolved.
Show resolved Hide resolved
*/
navigate(path, sessionId, preserveView, modalSettings, splitViewSettings) {
if (this.options.errorSkipNavigation) {
Expand All @@ -61,12 +62,14 @@ export class linkManager extends LuigiClientBase {

this.options.preserveView = preserveView;
const relativePath = path[0] !== '/';
const intentPath = path.includes('?Intent=');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow or prefer lowercase intent? camelcase is somehow a bit unusual for parameters

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 will ask this in afterscrum too 👍

Copy link
Contributor Author

@ndricimrr ndricimrr Oct 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

talked w/Philipp and chose to go with case insensitive for this one so Intent and intent (+any other case insensitive combination of the keyword "intent") should now work.

const navigationOpenMsg = {
msg: 'luigi.navigation.open',
sessionId: sessionId,
params: Object.assign(this.options, {
link: path,
relative: relativePath,
intent: intentPath,
modal: modalSettings,
splitView: splitViewSettings
})
Expand Down
3 changes: 3 additions & 0 deletions core/src/App.html
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,9 @@
n => navigationContext === n.navigationContext
);
path = Routing.concatenatePath(getSubPath(node), params.link);
} else if (params.intent) {
const intentPath = RoutingHelpers.getIntentPath(params.link);
path = intentPath ? intentPath : path;
} else if (params.relative) {
// relative
path = Routing.concatenatePath(getSubPath(currentNode), params.link);
Expand Down
24 changes: 23 additions & 1 deletion core/src/services/routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,43 @@ class RoutingClass {
}

getHashPath(url = window.location.hash) {
// check for intent, if any
if (url && url.includes('?Intent=')) {
const hash = url.replace('#/#', '#'); // handle default hash and intent specific hash
const intentHash = RoutingHelpers.getIntentPath(hash.split('#')[1]);
if (intentHash) {
return intentHash;
}
}

return url.split('#/')[1];
}

getModifiedPathname() {
// check for intent, if any
if (window.location.hash && window.location.hash.includes('?Intent=')) {
const hash = window.location.hash.replace('#/#', '').replace('#', '');
const intentPath = RoutingHelpers.getIntentPath(hash);
return intentPath ? intentPath : '/';
}
const path =
(window.history.state && window.history.state.path) ||
window.location.pathname;

return path
.split('/')
.slice(1)
.join('/');
}

getCurrentPath() {
if (window.location.hash.includes('?Intent=')) {
const hash = window.location.hash.replace('#/#', '').replace('#', '');
const intentPath = RoutingHelpers.getIntentPath(hash);
if (intentPath) {
// if intent faulty or illegal then skip
return intentPath;
}
}
return LuigiConfig.getConfigValue('routing.useHashRouting')
? window.location.hash.replace('#', '') // TODO: GenericHelpers.getPathWithoutHash(window.location.hash) fails in ContextSwitcher
: window.location.search
Expand Down
131 changes: 129 additions & 2 deletions core/src/utilities/helpers/routing-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,11 @@ class RoutingHelpersClass {

getNodeHref(node, pathParams) {
if (LuigiConfig.getConfigBooleanValue('navigation.addNavHrefs')) {
const link = RoutingHelpers.getRouteLink(node, pathParams,
LuigiConfig.getConfigValue('routing.useHashRouting')?"#":'');
const link = RoutingHelpers.getRouteLink(
node,
pathParams,
LuigiConfig.getConfigValue('routing.useHashRouting') ? '#' : ''
);
return link.url || link;
}
return 'javascript:void(0)';
Expand Down Expand Up @@ -264,6 +267,130 @@ class RoutingHelpersClass {
featureToggleList.forEach(ft => LuigiFeatureToggles.setFeatureToggle(ft));
}
}

/**
* This function takes an intentLink and parses it conforming certain limitations in characters usage.
* Limitations include:
* - `semanticObject` allows only alphanumeric characters
* - `action` allows alphanumeric characters and the '_' sign
*
* Example of resulting output:
* ```
* {
* semanticObject: "Sales",
* action: "order",
* params: [{param1: "value1"},{param2: "value2"}]
* };
* ```
* @param {string} link the intent link represents the semantic intent defined by the user
* i.e.: #?Intent=semanticObject-action?param=value
*/
getIntentObject(intentLink) {
const intentParams = intentLink.split('?Intent=')[1];
if (intentParams) {
const elements = intentParams.split('-');
if (elements.length === 2) {
// avoids usage of '-' in semantic object and action
const semanticObject = elements[0];
const actionAndParams = elements[1].split('?');
// length 2 involves parameters, length 1 involves no parameters
if (actionAndParams.length === 2 || actionAndParams.length === 1) {
let action = actionAndParams[0];
let params = actionAndParams[1];
// parse parameters, if any
if (params) {
params = params.split('&');
let paramObject = [];
ndricimrr marked this conversation as resolved.
Show resolved Hide resolved
params.forEach(item => {
const param = item.split('=');
param.length === 2 && paramObject.push({ [param[0]]: param[1] });
});
params = paramObject;
}
const alphanumeric = /^[0-9a-zA-Z]+$/;
const alphanumericOrUnderscore = /^[0-9a-zA-Z_]+$/;
ndricimrr marked this conversation as resolved.
Show resolved Hide resolved
// TODO: check for character size limit
if (
semanticObject.match(alphanumeric) &&
action.match(alphanumericOrUnderscore)
) {
return {
semanticObject,
action,
params
};
} else {
console.warn(
'Intent found contains illegal characters. Semantic object must be alphanumeric, action must be (alphanumeric+underscore)'
);
}
}
}
}
return false;
}

/**
* This function compares the intentLink parameter with the configuration intentMapping
* and returns the path segment that is matched together with the parameters, if any
*
* Example:
*
* For intentLink = `#?Intent=Sales-order?foo=bar`
* and Luigi configuration:
* ```
* intentMapping: [{
* semanticObject: 'Sales',
* action: 'order',
* pathSegment: '/projects/pr2/order'
* }]
* ```
* the given intentLink is matched with the configuration's same semanticObject and action,
* resulting in pathSegment `/projects/pr2/order` being returned. The parameter is also added in
* this case resulting in: `/projects/pr2/order?~foo=bar`
* @param {string} intentLink the intentLink represents the semantic intent defined by the user
* i.e.: #?Intent=semanticObject-action?param=value
*/
getIntentPath(intentLink) {
const mappings = LuigiConfig.getConfigValue('navigation.intentMapping');
if (mappings && mappings.length > 0) {
const intentObject = this.getIntentObject(intentLink);
if (intentObject) {
let realPath = mappings.find(
item =>
item.semanticObject === intentObject.semanticObject &&
item.action === intentObject.action
);
if (!realPath) {
return false;
}
realPath = realPath.pathSegment;
if (intentObject.params) {
// get custom node param prefixes if any or default to ~
let nodeParamPrefix = LuigiConfig.getConfigValue(
'routing.nodeParamPrefix'
);
nodeParamPrefix = nodeParamPrefix ? nodeParamPrefix : '~';
realPath = realPath.concat(`?${nodeParamPrefix}`);
intentObject.params.forEach(param => {
realPath = realPath.concat(Object.keys(param)[0]); // append param name
realPath = realPath.concat('=');
// append param value and prefix in case of multiple params
realPath = realPath
.concat(param[Object.keys(param)[0]])
.concat(`&${nodeParamPrefix}`);
});
realPath = realPath.slice(0, -(nodeParamPrefix.length + 1)); // slice extra prefix
}
return realPath;
} else {
console.warn('Could not parse given intent link.');
}
} else {
console.warn('No intent mappings are defined in Luigi configuration.');
}
return false;
}
}

export const RoutingHelpers = new RoutingHelpersClass();
Loading