diff --git a/client/luigi-client.d.ts b/client/luigi-client.d.ts index b3a1734a9b..5dc9ab9f12 100644 --- a/client/luigi-client.d.ts +++ b/client/luigi-client.d.ts @@ -449,6 +449,17 @@ export declare interface LinkManager { * LuigiClient.linkManager().preserveQueryParams(false).navigate('/projects/xy/foobar'); */ preserveQueryParams: (preserve: boolean) => this; + + /** + * Gets the luigi route associated with the current micro frontend. + * @returns {promise} a promise which resolves to a String value specifying the current luigi route + * @since NEXTRELEASE + * @example + * LuigiClient.linkManager().getCurrentRoute(); + * LuigiClient.linkManager().fromContext('project').getCurrentRoute(); + * LuigiClient.linkManager().fromVirtualTreeRoot().getCurrentRoute(); + */ + getCurrentRoute : () => Promise; } export declare interface StorageManager { diff --git a/client/src/linkManager.js b/client/src/linkManager.js index 9c65154e71..ddea9256bd 100644 --- a/client/src/linkManager.js +++ b/client/src/linkManager.js @@ -423,4 +423,53 @@ export class linkManager extends LuigiClientBase { this.options.preserveQueryParams = preserve; return this; } + + /** + * Gets the luigi route associated with the current micro frontend. + * @returns {promise} a promise which resolves to a String value specifying the current luigi route + * @since NEXTRELEASE + * @example + * LuigiClient.linkManager().getCurrentRoute(); + * LuigiClient.linkManager().fromContext('project').getCurrentRoute(); + * LuigiClient.linkManager().fromVirtualTreeRoot().getCurrentRoute(); + */ + getCurrentRoute() { + const currentId = helpers.getRandomId(); + + const currentRoutePromise = this.getPromise('getCurrentRoute') || {}; + currentRoutePromise[currentId] = { + resolveFn: function() {}, + then: function(resolveFn) { + this.resolveFn = resolveFn; + } + }; + + this.setPromise('getCurrentRoute', currentRoutePromise); + + helpers.addEventListener( + 'luigi.navigation.currentRoute.answer', (e, listenerId) => { + + const data = e.data.data; + const currentRoutePromise = this.getPromise('getCurrentRoute') || {}; + + if (data.correlationId === currentId ) { + if (currentRoutePromise[data.correlationId]) { + currentRoutePromise[data.correlationId].resolveFn(data.route); + delete currentRoutePromise[data.correlationId]; + this.setPromise('getCurrentRoute', currentRoutePromise); + } + helpers.removeEventListener(listenerId); + } + helpers.removeEventListener(listenerId); + }); + + helpers.sendPostMessageToLuigiCore({ + msg: 'luigi.navigation.currentRoute', + data: Object.assign(this.options, { + id: currentId, + }) + }); + + return currentRoutePromise[currentId]; + } } diff --git a/core/src/App.svelte b/core/src/App.svelte index faf0eb0e15..714661422c 100644 --- a/core/src/App.svelte +++ b/core/src/App.svelte @@ -415,7 +415,9 @@ const buildPath = (params, srcNode, srcNodePathParams) => { const localNode = srcNode || currentNode; - const localPathParams = srcNodePathParams || pathParams; + const localPathParams = GenericHelpers.isEmptyObject(srcNodePathParams) + ? pathParams + : srcNodePathParams; let localNavPath = navigationPath; if (srcNode) { let parent = srcNode.parent; @@ -441,6 +443,21 @@ getSubPath(node, localPathParams), params.link ); + + // boolean predicate, changes the path only if getCurrentPath function used + const isGetCurrentPathRequired = + !GenericHelpers.isEmptyObject(localPathParams) && + !path.includes('virtualSegment_') && + !params.link && + params.getCurrentPath && + Object.keys(localPathParams)[0].includes('virtualSegment_'); + if (isGetCurrentPathRequired) { + let virtualPath = ''; + Object.entries(localPathParams).forEach((virtualParam) => { + virtualPath += '/' + virtualParam[1]; + }); + path = virtualPath; + } } else if (params.fromParent) { // from direct parent path = Routing.concatenatePath( @@ -462,10 +479,11 @@ const node = [...localNavPath] .reverse() .find((n) => navigationContext === n.navigationContext); - path = Routing.concatenatePath( - getSubPath(node, localPathParams), - params.link - ); + const pathUpToContext = getSubPath(node, localPathParams); + const fullPath = getSubPath(localNode, localPathParams); + path = params.getCurrentPath + ? fullPath.substring(pathUpToContext.length) + : Routing.concatenatePath(pathUpToContext, params.link); } else if (params.intent) { path = RoutingHelpers.getIntentPath(params.link); } else if (params.relative) { @@ -474,6 +492,11 @@ getSubPath(localNode, localPathParams), params.link ); + } else { + // retrieve path for getCurrentPath method when no options used + if (params.getCurrentPath) { + path = getSubPath(localNode, localPathParams); + } } if (params.nodeParams && Object.keys(params.nodeParams).length > 0) { path += path.includes('?') ? '&' : '?'; @@ -1291,20 +1314,24 @@ const isSpecial = newTab || modal || splitView || drawer; const resolveRemotePromise = () => { - const remotePromise = GenericHelpers.getRemotePromise(e.data.remotePromiseId); + const remotePromise = GenericHelpers.getRemotePromise( + e.data.remotePromiseId + ); if (remotePromise) { remotePromise.doResolve(); } }; const rejectRemotePromise = () => { - const remotePromise = GenericHelpers.getRemotePromise(e.data.remotePromiseId); + const remotePromise = GenericHelpers.getRemotePromise( + e.data.remotePromiseId + ); if (remotePromise) { remotePromise.doReject(); } }; - const checkResolve = checkLocationChange => { + const checkResolve = (checkLocationChange) => { if (!checkLocationChange || previousUrl !== window.location.href) { resolveRemotePromise(); } else { @@ -1428,6 +1455,25 @@ } } + // handle getCurrentRoute message coming from client + if ('luigi.navigation.currentRoute' === e.data.msg) { + const srcNode = iframe.luigi.currentNode; + const srcPathParams = iframe.luigi.pathParams; + const data = e.data.data; + data.getCurrentPath = true; + const path = buildPath(data, srcNode, srcPathParams); + + // send answer back to client + const message = { + msg: 'luigi.navigation.currentRoute.answer', + data: { + route: path, + correlationId: data.id, + }, + }; + IframeHelpers.sendMessageToIframe(iframe, message); + } + if ('luigi.auth.tokenIssued' === e.data.msg) { sendAuthDataToClient(e.data.authData); } diff --git a/core/src/utilities/helpers/generic-helpers.js b/core/src/utilities/helpers/generic-helpers.js index def9966ecd..c7d127a0ac 100644 --- a/core/src/utilities/helpers/generic-helpers.js +++ b/core/src/utilities/helpers/generic-helpers.js @@ -52,7 +52,16 @@ class GenericHelpersClass { * @returns {boolean} */ isObject(objectToCheck) { - return objectToCheck && typeof objectToCheck === 'object' && !Array.isArray(objectToCheck); + return !!(objectToCheck && typeof objectToCheck === 'object' && !Array.isArray(objectToCheck)); + } + + /** + * Check if object is empty + * @param item object to check + * @returns {boolean} + */ + isEmptyObject(item) { + return this.isObject(item) && Object.keys(item).length === 0; } /** diff --git a/core/test/utilities/helpers/generic-helpers.spec.js b/core/test/utilities/helpers/generic-helpers.spec.js index 557b0b6318..6e33758084 100644 --- a/core/test/utilities/helpers/generic-helpers.spec.js +++ b/core/test/utilities/helpers/generic-helpers.spec.js @@ -81,6 +81,15 @@ describe('Generic-helpers', () => { assert.equal(GenericHelpers.isObject(12345), false); }); + it('isEmptyObject', () => { + const obj = { foo: 'bar' }; + const obj2 = {}; + const obj3 = undefined; + assert.equal(GenericHelpers.isEmptyObject(obj), false); + assert.equal(GenericHelpers.isEmptyObject(obj2), true); + assert.equal(GenericHelpers.isEmptyObject(obj3), false); + }); + it('removeInternalProperties', () => { const input = { some: true, diff --git a/test/e2e-test-application/e2e/tests/1-angular/luigi-client-link-manager-features-4.spec.js b/test/e2e-test-application/e2e/tests/1-angular/luigi-client-link-manager-features-4.spec.js index 654c6bae91..c26b3db45b 100644 --- a/test/e2e-test-application/e2e/tests/1-angular/luigi-client-link-manager-features-4.spec.js +++ b/test/e2e-test-application/e2e/tests/1-angular/luigi-client-link-manager-features-4.spec.js @@ -333,4 +333,92 @@ describe('Luigi Client linkManager Webcomponent, Drawer', () => { }); }); }); + + describe('getCurrentRoute', () => { + let $iframeBody; + beforeEach(() => { + // "clear" variables to make sure they are not reused and throw error in case something goes wrong + $iframeBody = undefined; + cy.visitLoggedIn('/'); + }); + + it('with fromContext option', () => { + const routeToCheck = '/projects/pr1/users/groups/avengers'; + const routeFromContext = '/users/groups/avengers'; + cy.visitLoggedIn(routeToCheck); + + cy.expectPathToBe(routeToCheck); + + cy.getIframeBody().then(result => { + $iframeBody = result; + cy.wrap($iframeBody).contains('This is a dynamic node with'); + + cy.wrap($iframeBody) + .find('[data-testid="curr-button"]') + .click(); + + cy.wrap($iframeBody) + .find('[data-testid="curr-button"]') + .click(); + + cy.wrap($iframeBody) + .find('[data-testid="curr-text"]') + .should('contain', routeFromContext); + }); + }); + + it('with fromVirtualTree option', () => { + const routeToCheck = '/projects/pr1/developers'; + const routeVirtual = '/internal/virtualTree'; + cy.visitLoggedIn(routeToCheck); + cy.expectPathToBe(routeToCheck); + + cy.getIframeBody().then(result => { + $iframeBody = result; + cy.wrap($iframeBody).contains('Developers content.'); + + cy.wrap($iframeBody) + .find('[data-testid="goToVirtualTree"]') + .click(); + + cy.expectPathToBe(routeToCheck + routeVirtual); + + cy.wrap($iframeBody) + .find('[data-testid="curr-link-virtualtree"]') + .click(); + + cy.wrap($iframeBody) + .find('[data-testid="curr-link-virtualtree"]') + .click(); + + cy.wrap($iframeBody) + .find('[data-testid="curr-text-virtualtree"]') + .should('contain', routeVirtual); + }); + }); + + it('with no option', () => { + const routeToCheck = '/projects/pr2/settings'; + cy.visitLoggedIn(routeToCheck); + cy.expectPathToBe(routeToCheck); + + cy.getIframeBody().then(result => { + $iframeBody = result; + // cy.wrap($iframeBody) + // .contains('Developers content.') + + cy.wrap($iframeBody) + .find('[data-testid="curr-link-no-option"]') + .click(); + + cy.wrap($iframeBody) + .find('[data-testid="curr-link-no-option"]') + .click(); + + cy.wrap($iframeBody) + .find('[data-testid="curr-text-no-option"]') + .should('contain', routeToCheck); + }); + }); + }); }); diff --git a/test/e2e-test-application/src/app/project/developers/developers.component.html b/test/e2e-test-application/src/app/project/developers/developers.component.html index b7031801b4..02c5d59802 100644 --- a/test/e2e-test-application/src/app/project/developers/developers.component.html +++ b/test/e2e-test-application/src/app/project/developers/developers.component.html @@ -7,7 +7,7 @@

Developers

Developers content.

- Navigate internaly using Angular router and LuigiClient's withoutSync to /internal/virtualTree diff --git a/test/e2e-test-application/src/app/project/settings/settings.component.html b/test/e2e-test-application/src/app/project/settings/settings.component.html index d11648ca34..bbdf2674b7 100644 --- a/test/e2e-test-application/src/app/project/settings/settings.component.html +++ b/test/e2e-test-application/src/app/project/settings/settings.component.html @@ -136,6 +136,32 @@

Navigate

+
+
+
+ + +

{{currentRoute}}

+
+
+
diff --git a/test/e2e-test-application/src/app/project/users/groups/group-details/group-details.component.ts b/test/e2e-test-application/src/app/project/users/groups/group-details/group-details.component.ts index 6e9d2ee69c..210b7c0467 100644 --- a/test/e2e-test-application/src/app/project/users/groups/group-details/group-details.component.ts +++ b/test/e2e-test-application/src/app/project/users/groups/group-details/group-details.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { getPathParams, linkManager } from '@luigi-project/client'; - import { LuigiContextService, IContextMessage } from '../../../../services/luigi-context.service'; import { toTitleCase } from '../../../../services/helpers'; @@ -15,6 +14,7 @@ export class GroupDetailsComponent implements OnInit, OnDestroy { public pathParams: { [key: string]: string }; public groupLabel: string; private lcSubscription: Subscription; + public currentRoute: string; constructor(private luigiService: LuigiContextService, private cdr: ChangeDetectorRef) {} @@ -38,4 +38,13 @@ export class GroupDetailsComponent implements OnInit, OnDestroy { this.lcSubscription.unsubscribe(); } } + + getCurrentRoute() { + linkManager() + .fromContext('project') + .getCurrentRoute() + .then(route => { + this.currentRoute = route; + }); + } }