diff --git a/README.md b/README.md index 5f17b138..5b3be4c4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ https://api.reuse.software/badge/github.com/openmfp/portal-ui-lib)](https://api. This library helps you to set up your angular project consuming [Luigi](https://luigi-project.io/) configuration. -Main features of this library are: +The main features of this library are: * Enable dynamic Luigi configuration consumption in a microfrontend. * Provide authentication capabilities with Auth Server @@ -28,7 +28,7 @@ Main features of this library are: - [The luigiExtendedGlobalContextConfigService Option](#the-luigiextendedglobalcontextconfigservice-option) - [The userSettingsConfigService Option](#the-usersettingsconfigservice-option) - [The globalSearchConfigService option](#the-globalsearchconfigservice-option) - - [The luigiBreadcrumbConfigService Option](#the-luigibreadcrumbconfigservice-option) + - [The luigiBreadcrumbConfigService Option](#the-headerBarConfigService-option) - [The userProfileConfigService Option](#the-userprofileconfigservice-option) - [Functional Services](#functional-services) - [The luigiAuthEventsCallbacksService Option](#the-luigiautheventscallbacksservice-option) @@ -165,7 +165,7 @@ export class StaticSettingsConfigServiceImpl { constructor() {} - getInitialStaticSettingsConfig() { + getStaticSettingsConfig() { const logo = 'assets/my-logo.svg'; return { @@ -181,20 +181,9 @@ export class StaticSettingsConfigServiceImpl // ... the rest of the configuration }; } - - getStaticSettingsConfig() { - return { - ...this.getInitialStaticSettingsConfig(), - appLoadingIndicator: { - hideAutomatically: true, - }, - // ... the rest of the configuration - } - } } ``` -The `getInitialStaticSettingsConfig()` method is called while constructing the Luigi initial config object. The `getStaticSettingsConfig()` is called while [Luigi lifecycle hook luigiAfterInit](https://docs.luigi-project.io/docs/lifecycle-hooks?section=luigiafterinit). The latter adds additional setup to the root of the Luigi configuration object. @@ -312,6 +301,7 @@ By default, in the [Luigi's global context](https://docs.luigi-project.io/docs/n ```json { "portalContext": {...} , + "portalBaseUrl": "portal base url", "userId": "logged in user id", "userEmail": "logged in user email", "token": "id token of the logged in user" @@ -464,7 +454,7 @@ In your `main.ts` you can provide your custom implementation like so: ```ts const portalOptions: PortalOptions = { - luigiBreadcrumbConfigService: LuigiBreadcrumbConfigServiceImpl, + headerBarConfigService: HeaderBarConfigServiceImpl, // ... other portal options } ``` @@ -547,7 +537,7 @@ const portalOptions: PortalOptions = { With this option it is possible to define listeners for [Luigi custom messages](https://docs.luigi-project.io/docs/communication?section=custom-messages). Custom messages are sent from any part of your application to Luigi and then routed to any other micro frontend application in the same application. -A custom message is sent by using Luigis `sendCustomMessage({ id: 'unique.message.id'});` method (see also the following example). +A custom message is sent by using Luigi `sendCustomMessage({ id: 'unique.message.id'});` method (see also the following example). ```ts import { inject, Injectable } from '@angular/core'; @@ -563,7 +553,7 @@ export class MessageService { } ``` -In order to react on such a message in your micro frontend, you have to provide a custom message listener. +To react on such a message in your micro frontend, you have to provide a custom message listener. You have to specify the corresponding message id you want to listen to. If there is a match, the callback function `onCustomMessageReceived()` will be called. Make sure to return a valid Luigi configuration object. @@ -617,7 +607,7 @@ export class CustomGlobalNodesServiceImpl implements CustomGlobalNodesService { private async createGlobalNode1(): LuigiNode { return { label: 'Global 1', - entityType: 'global.topnav', + entityType: 'global', link: '/global_1', // ... other luigi node properties }; diff --git a/projects/lib/src/lib/initializers/session-refresh.initializer.spec.ts b/projects/lib/src/lib/initializers/session-refresh.initializer.spec.ts index a17b3e7e..14c88f5d 100644 --- a/projects/lib/src/lib/initializers/session-refresh.initializer.spec.ts +++ b/projects/lib/src/lib/initializers/session-refresh.initializer.spec.ts @@ -36,14 +36,12 @@ describe('Session Refresh Provider', () => { provideSessionRefresh(), { provide: SessionRefreshService, useValue: sessionRefreshServiceMock }, { provide: AuthService, useValue: authServiceMock }, - { provide: LuigiCoreService, useValue: luigiCoreServiceMock }, ], }); initializeAutomaticSessionRefresh( sessionRefreshServiceMock, authServiceMock, - luigiCoreServiceMock, ); }); @@ -52,15 +50,12 @@ describe('Session Refresh Provider', () => { }); describe('Session refresh initialization', () => { - it('should subscribe and handle AUTH_EXPIRE_SOON event when feature is enabled', fakeAsync(() => { + it('should subscribe and handle AUTH_EXPIRE_SOON event', fakeAsync(() => { // Act authEventsSubject.next(AuthEvent.AUTH_EXPIRE_SOON); tick(); // Assert - expect(luigiCoreServiceMock.isFeatureToggleActive).toHaveBeenCalledWith( - 'enableSessionAutoRefresh', - ); expect(sessionRefreshServiceMock.refresh).toHaveBeenCalledTimes(1); })); @@ -73,18 +68,6 @@ describe('Session Refresh Provider', () => { expect(sessionRefreshServiceMock.refresh).not.toHaveBeenCalled(); })); - it('should not call refresh when feature toggle is disabled', fakeAsync(() => { - // Arrange - luigiCoreServiceMock.isFeatureToggleActive.mockReturnValue(false); - - // Act - authEventsSubject.next(AuthEvent.AUTH_EXPIRE_SOON); - tick(); - - // Assert - expect(sessionRefreshServiceMock.refresh).not.toHaveBeenCalled(); - })); - it('should handle refresh failures gracefully', fakeAsync(() => { try { // Arrange @@ -115,25 +98,6 @@ describe('Session Refresh Provider', () => { expect(sessionRefreshServiceMock.refresh).toHaveBeenCalledTimes(2); })); - it('should check feature toggle for each event', fakeAsync(() => { - // Arrange - luigiCoreServiceMock.isFeatureToggleActive - .mockReturnValueOnce(true) - .mockReturnValueOnce(false); - - // Act - authEventsSubject.next(AuthEvent.AUTH_EXPIRE_SOON); - tick(); - authEventsSubject.next(AuthEvent.AUTH_EXPIRE_SOON); - tick(); - - // Assert - expect(luigiCoreServiceMock.isFeatureToggleActive).toHaveBeenCalledTimes( - 2, - ); - expect(sessionRefreshServiceMock.refresh).toHaveBeenCalledTimes(1); - })); - it('should complete subscription when subject is completed', fakeAsync(() => { // Act authEventsSubject.complete(); diff --git a/projects/lib/src/lib/initializers/session-refresh.initializer.ts b/projects/lib/src/lib/initializers/session-refresh.initializer.ts index 779d1474..df83ef23 100644 --- a/projects/lib/src/lib/initializers/session-refresh.initializer.ts +++ b/projects/lib/src/lib/initializers/session-refresh.initializer.ts @@ -1,24 +1,14 @@ import { AuthEvent } from '../models'; -import { - AuthService, - LuigiCoreService, - SessionRefreshService, -} from '../services'; +import { AuthService, SessionRefreshService } from '../services'; import { inject, provideAppInitializer } from '@angular/core'; import { filter } from 'rxjs'; export async function initializeAutomaticSessionRefresh( sessionRefreshService: SessionRefreshService, authService: AuthService, - luigiCoreService: LuigiCoreService, ) { authService.authEvents - .pipe( - filter((event: AuthEvent) => event === AuthEvent.AUTH_EXPIRE_SOON), - filter(() => - luigiCoreService.isFeatureToggleActive('enableSessionAutoRefresh'), - ), - ) + .pipe(filter((event: AuthEvent) => event === AuthEvent.AUTH_EXPIRE_SOON)) .subscribe({ next: async () => { try { @@ -35,6 +25,5 @@ export const provideSessionRefresh = () => initializeAutomaticSessionRefresh( inject(SessionRefreshService), inject(AuthService), - inject(LuigiCoreService), ), ); diff --git a/projects/lib/src/lib/models/env.ts b/projects/lib/src/lib/models/env.ts index 9d4e307f..504d3131 100644 --- a/projects/lib/src/lib/models/env.ts +++ b/projects/lib/src/lib/models/env.ts @@ -1,6 +1,6 @@ export interface AuthTokenData { expires_in: string; - access_token: string; + access_token?: string; id_token: string; } diff --git a/projects/lib/src/lib/models/user.ts b/projects/lib/src/lib/models/user.ts index c0caa1fa..d9eba90b 100644 --- a/projects/lib/src/lib/models/user.ts +++ b/projects/lib/src/lib/models/user.ts @@ -2,8 +2,7 @@ export interface UserData { name: string; email: string; description: string; - picture: string; - icon: boolean; + icon: string; initials: string; userId: string; } @@ -15,5 +14,7 @@ export interface UserTokenData { family_name: string; mail: string; email: string; + userId: string; + icon: string; sub: string; } diff --git a/projects/lib/src/lib/portal-providers.ts b/projects/lib/src/lib/portal-providers.ts index f3467b5e..2f802d86 100644 --- a/projects/lib/src/lib/portal-providers.ts +++ b/projects/lib/src/lib/portal-providers.ts @@ -42,7 +42,6 @@ import { StaticSettingsConfigService, ThemingService, UserProfileConfigService, - UserSettingsConfigService, } from './services'; import { CustomReuseStrategy } from './utilities'; import { provideHttpClient } from '@angular/common/http'; @@ -64,9 +63,6 @@ export interface PortalOptions { /** Service providing local configuration services **/ localConfigurationService?: Type; - /** Service providing user setting specific configuration **/ - userSettingsConfigService?: Type; - /** Service providing global search configuration **/ globalSearchConfigService?: Type; diff --git a/projects/lib/src/lib/services/luigi-config/auth-config.service.spec.ts b/projects/lib/src/lib/services/luigi-config/auth-config.service.spec.ts index 95e2680d..25937c31 100644 --- a/projects/lib/src/lib/services/luigi-config/auth-config.service.spec.ts +++ b/projects/lib/src/lib/services/luigi-config/auth-config.service.spec.ts @@ -71,7 +71,7 @@ describe('AuthConfigService', () => { it('should handle userInfoFn correctly', async () => { const userInfo = { name: 'Test User', - picture: 'https://example.com/pic.jpg', + icon: 'https://example.com/pic.jpg', } as UserData; authServiceMock.getUserInfo.mockReturnValue(userInfo); @@ -89,37 +89,6 @@ describe('AuthConfigService', () => { const result = await userInfoFn?.(); expect(result).toEqual(userInfo); - expect(global.fetch).toHaveBeenCalledWith( - userInfo.picture, - expect.any(Object), - ); - }); - - it('should handle userInfoFn when fetch fails', async () => { - const userPicture = 'https://example.com/pic.jpg'; - const userInfo = { - name: 'Test User', - picture: userPicture, - } as UserData; - authServiceMock.getUserInfo.mockReturnValue(userInfo); - - envConfigServiceMock.getEnvConfig.mockResolvedValue({ - oauthServerUrl: 'https://example.com/oauth', - clientId: 'client-id', - baseDomain: 'https://example.com', - } as any); - const config = await service.getAuthConfig(); - const userInfoFn = config?.oAuth2AuthCode?.userInfoFn; - - global.fetch = jest.fn().mockRejectedValue(new Error('Fetch failed')); - - const result = await userInfoFn?.(); - - expect(result).toEqual({ ...userInfo, picture: '' }); - expect(global.fetch).toHaveBeenCalledWith( - userPicture, - expect.any(Object), - ); }); }); diff --git a/projects/lib/src/lib/services/luigi-config/auth-config.service.ts b/projects/lib/src/lib/services/luigi-config/auth-config.service.ts index 409a69a0..0167161b 100644 --- a/projects/lib/src/lib/services/luigi-config/auth-config.service.ts +++ b/projects/lib/src/lib/services/luigi-config/auth-config.service.ts @@ -43,19 +43,8 @@ export class AuthConfigService { expirationCheckInterval: 5, userInfoFn: () => { const userInfo = this.authService.getUserInfo(); - return new Promise((resolve) => { - fetch(userInfo.picture, { - method: 'HEAD', - mode: 'no-cors', - }) - .then(() => { - resolve(userInfo); - }) - .catch(() => { - userInfo.picture = ''; - resolve(userInfo); - }); + resolve(userInfo); }); }, }, diff --git a/projects/lib/src/lib/services/luigi-config/user-settings-config.service.ts b/projects/lib/src/lib/services/luigi-config/user-settings-config.service.ts index 2da794eb..30829f6a 100644 --- a/projects/lib/src/lib/services/luigi-config/user-settings-config.service.ts +++ b/projects/lib/src/lib/services/luigi-config/user-settings-config.service.ts @@ -199,8 +199,7 @@ export class UserSettingsConfigService { luigiUserSettings.forEach((userConfig) => { if (userConfig?.groups) { Object.keys(userConfig.groups).forEach((groupId) => { - const groupConfig = userConfig.groups[groupId]; - settingsGroups[groupId] = groupConfig; + settingsGroups[groupId] = userConfig.groups[groupId]; }); } }); @@ -262,7 +261,7 @@ export class UserSettingsConfigService { settings.frame_userAccount = { label: 'USERSETTINGSDIALOG_USER_ACCOUNT', sublabel: userInfo.name, - icon: imageUrl || 'person-placeholder', + icon: imageUrl || userInfo.icon || 'person-placeholder', title: userInfo.name, initials: userInfo.initials, iconClassAttribute: diff --git a/projects/lib/src/lib/services/portal/auth.service.spec.ts b/projects/lib/src/lib/services/portal/auth.service.spec.ts index 12f122ca..c28515c6 100644 --- a/projects/lib/src/lib/services/portal/auth.service.spec.ts +++ b/projects/lib/src/lib/services/portal/auth.service.spec.ts @@ -132,10 +132,8 @@ describe('AuthService', () => { expect(service.getUserInfo()).toEqual({ description: '', email: '', - icon: false, initials: '', name: ' ', - picture: '', userId: '', }); }); @@ -188,9 +186,7 @@ describe('AuthService', () => { name: 'John Doe', email: 'john.doe@example.com', description: 'john.doe@example.com', - picture: '', userId: 'user123', - icon: false, initials: 'JD', }); }); @@ -207,8 +203,6 @@ describe('AuthService', () => { name: ' ', email: 'john.doe@example.com', description: 'john.doe@example.com', - picture: '', - icon: false, initials: '', userId: 'user123', }); diff --git a/projects/lib/src/lib/services/portal/auth.service.ts b/projects/lib/src/lib/services/portal/auth.service.ts index 036af90a..b376d83e 100644 --- a/projects/lib/src/lib/services/portal/auth.service.ts +++ b/projects/lib/src/lib/services/portal/auth.service.ts @@ -87,10 +87,9 @@ export class AuthService { name: `${firstName} ${lastName}`, email: user.mail || user.email || '', description: user.mail || user.email || '', - picture: '', - icon: false, + icon: user.icon, initials: initialsFirstName + initialsLastName, - userId: user.sub || '', + userId: user.userId || user.sub || '', }; }