[AAE-10779] User info component refactor (#8187)

* [AAE-10779] Update documentation

* [AAE-10779] Update demo-shell user-info component call

* [AAE-10779] Ecm user info component

* [AAE-10779] Identity user info component

* [AAE-10779] Bpm user info component

* [AAE-10779] Remove ecm-panel id references

* [AAE-10779] add stories and remove old component

* [AAE-10779] Update doc version and remove leftover html tag

* trigger travis

* [AAE-10779] rename ecm-user-info to content-user-info and bpm-user-info to process-user-info

* [AAE-10779] update docs

* [AAE-10779] fix demo-shell user-info

* [AAE-10779] add docs
This commit is contained in:
Diogo Bastos
2023-02-15 14:47:43 +00:00
committed by GitHub
parent c5710c0e61
commit 96075ae456
43 changed files with 2057 additions and 1210 deletions

View File

@@ -115,6 +115,7 @@ import localeSv from '@angular/common/locales/sv';
import { setupAppNotifications } from './services/app-notifications-factory';
import { AppNotificationsService } from './services/app-notifications.service';
import { SearchFilterChipsComponent } from './components/search/search-filter-chips.component';
import { UserInfoComponent } from './components/app-layout/user-info/user-info.component';
registerLocaleData(localeFr);
registerLocaleData(localeDe);
@@ -159,6 +160,7 @@ registerLocaleData(localeSv);
AppComponent,
LogoutComponent,
AppLayoutComponent,
UserInfoComponent,
HomeComponent,
SearchBarComponent,
SearchResultComponent,

View File

@@ -22,8 +22,7 @@
<div class="app-header-delimiexpandedSidenavter"></div>
<adf-userinfo [menuPositionX]="'before'" [menuPositionY]="'above'">
</adf-userinfo>
<app-shell-user-info [menuPositionX]="'before'" [menuPositionY]="'above'"></app-shell-user-info>
<app-theme-picker></app-theme-picker>
<button data-automation-id="language-menu-button" mat-icon-button [matMenuTriggerFor]="langMenu">

View File

@@ -0,0 +1,21 @@
<ng-container>
<adf-content-user-info
*ngIf="mode === userInfoMode.CONTENT || mode === userInfoMode.CONTENT_SSO"
[ecmUser]="ecmUser$ | async"
[identityUser]="identityUser$ | async"
[isLoggedIn]="isLoggedIn"
[mode]="mode"
></adf-content-user-info>
<adf-identity-user-info
*ngIf="mode === userInfoMode.SSO"
[identityUser]="identityUser$ | async"
[isLoggedIn]="isLoggedIn"
></adf-identity-user-info>
<adf-process-user-info
*ngIf="mode === userInfoMode.PROCESS || mode === userInfoMode.ALL"
[bpmUser]="bpmUser$ | async"
[ecmUser]="ecmUser$ | async"
[isLoggedIn]="isLoggedIn"
[mode]="mode"
></adf-process-user-info>
</ng-container>

View File

@@ -15,34 +15,16 @@
* limitations under the License.
*/
import { Component, Input, OnInit, ViewEncapsulation, ViewChild, OnDestroy } from '@angular/core';
import { AuthenticationService } from '../../auth/services/authentication.service';
import { BpmUserModel } from '../../models/bpm-user.model';
import { EcmUserModel } from '../../models/ecm-user.model';
import { IdentityUserModel } from '../../auth/models/identity-user.model';
import { BpmUserService } from '../../services/bpm-user.service';
import { IdentityUserService } from '../../auth/services/identity-user.service';
import { of, Observable, Subject } from 'rxjs';
import { MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { PeopleContentService } from '../../services/people-content.service';
import { AuthenticationService, BpmUserModel, BpmUserService, EcmUserModel, IdentityUserModel, IdentityUserService, PeopleContentService, UserInfoMode } from '@alfresco/adf-core';
import { Component, OnInit, Input } from '@angular/core';
import { MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { Observable, of } from 'rxjs';
@Component({
selector: 'adf-userinfo',
templateUrl: './user-info.component.html',
styleUrls: ['./user-info.component.scss'],
encapsulation: ViewEncapsulation.None
selector: 'app-shell-user-info',
templateUrl: './user-info.component.html'
})
export class UserInfoComponent implements OnInit, OnDestroy {
@ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
/** Custom path for the background banner image for ACS users. */
@Input()
ecmBackgroundImage: string = './assets/images/ecm-background.png';
/** Custom path for the background banner image for APS users. */
@Input()
bpmBackgroundImage: string = './assets/images/bpm-background.png';
export class UserInfoComponent implements OnInit {
/** Custom choice for opening the menu at the bottom. Can be `before` or `after`. */
@Input()
@@ -52,23 +34,12 @@ export class UserInfoComponent implements OnInit, OnDestroy {
@Input()
menuPositionY: MenuPositionY = 'below';
/** Shows/hides the username next to the user info button. */
@Input()
showName: boolean = true;
/** When the username is shown, this defines its position relative to the user info button.
* Can be `right` or `left`.
*/
@Input()
namePosition: string = 'right';
mode: string;
mode: UserInfoMode;
ecmUser$: Observable<EcmUserModel>;
bpmUser$: Observable<BpmUserModel>;
identityUser$: Observable<IdentityUserModel>;
selectedIndex: number;
private destroy$ = new Subject();
userInfoMode = UserInfoMode;
constructor(private peopleContentService: PeopleContentService,
private bpmUserService: BpmUserService,
@@ -80,40 +51,26 @@ export class UserInfoComponent implements OnInit, OnDestroy {
this.getUserInfo();
}
ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
getUserInfo() {
if (this.authService.isOauth()) {
this.loadIdentityUserInfo();
this.mode = 'SSO';
this.mode = UserInfoMode.SSO;
if (this.authService.isECMProvider() && this.authService.isEcmLoggedIn()) {
this.mode = UserInfoMode.CONTENT_SSO;
this.loadEcmUserInfo();
}
} else if (this.isAllLoggedIn()) {
this.loadEcmUserInfo();
this.loadBpmUserInfo();
this.mode = 'ALL';
this.mode = UserInfoMode.ALL;
} else if (this.isEcmLoggedIn()) {
this.loadEcmUserInfo();
this.mode = 'CONTENT';
this.mode = UserInfoMode.CONTENT;
} else if (this.isBpmLoggedIn()) {
this.loadBpmUserInfo();
this.mode = 'PROCESS';
}
}
onKeyPress(event: KeyboardEvent) {
this.closeUserModal(event);
}
private closeUserModal($event: KeyboardEvent) {
if ($event.keyCode === 27) {
this.trigger.closeMenu();
this.mode = UserInfoMode.PROCESS;
}
}
@@ -147,20 +104,5 @@ export class UserInfoComponent implements OnInit, OnDestroy {
private isEcmLoggedIn() {
return this.authService.isEcmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled());
}
stopClosing(event: Event) {
event.stopPropagation();
}
getEcmAvatar(avatarId: any): string {
return this.peopleContentService.getUserProfileImage(avatarId);
}
getBpmUserImage(): string {
return this.bpmUserService.getCurrentUserProfileImage();
}
get showOnRight(): boolean {
return this.namePosition === 'right';
}
}

View File

@@ -0,0 +1,38 @@
---
Title: Content User Info component
Added: v6.0.0
Status: Active
Last reviewed: 2023-01-24
---
# [Content User Info component](../../../lib/content-services/src/lib/content-user-info/content-user-info.component.ts "Defined in content-user-info.component.ts")
Shows user information for `CONTENT` and `CONTENT_SSO` mode.
## Basic usage
```html
<adf-content-user-info></adf-content-user-info>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| isLoggedIn | `boolean` | Is user logged in |
| EcmUser | `EcmUserModel` | Ecm user model. |
| identityUser | `IdentityUserModel` | Identity user model. |
| mode | `UserInfoMode` | `UserInfoMode.CONTENT` | current mode. |
| bpmBackgroundImage | `string` | | Custom path for the background banner image for APS users. |
| EcmBackgroundImage | `string` | | Custom path for the background banner image for ACS users. |
| menuPositionX | [`MenuPositionX`](https://github.com/angular/components/blob/master/src/material/menu/menu-positions.ts) | "after" | Custom choice for opening the menu at the bottom. Can be `before` or `after`. |
| menuPositionY | [`MenuPositionY`](https://github.com/angular/components/blob/master/src/material/menu/menu-positions.ts) | "below" | Custom choice for opening the menu at the bottom. Can be `above` or `below`. |
| namePosition | `string` | "right" | When the username is shown, this defines its position relative to the user info button. Can be `right` or `left`. |
| showName | `boolean` | true | Shows/hides the username next to the user info button. |
## Details
The component shows a round icon for the user and will show extra information about
the user when clicked.

View File

@@ -1,18 +1,18 @@
---
Title: User Info component
Added: v2.0.0
Title: Identity User Info component
Added: v6.0.0
Status: Active
Last reviewed: 2018-11-19
Last reviewed: 2023-01-24
---
# [User Info component](../../../lib/core/src/lib/userinfo/components/user-info.component.ts "Defined in user-info.component.ts")
# [Identity User Info component](../../../lib/core/src/lib/identity-user-info/identity-user-info.component.ts "Defined in identityuser-info.component.ts")
Shows user information.
Shows user information for SSO mode.
## Basic usage
```html
<adf-userinfo></adf-userinfo>
<adf-identity-user-info></adf-identity-user-info>
```
## Class members
@@ -21,6 +21,8 @@ Shows user information.
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| isLoggedIn | `boolean` | Is user logged in |
| identityUser | `IdentityUserModel` | Identity user model. |
| bpmBackgroundImage | `string` | | Custom path for the background banner image for APS users. |
| ecmBackgroundImage | `string` | | Custom path for the background banner image for ACS users. |
| menuPositionX | [`MenuPositionX`](https://github.com/angular/components/blob/master/src/material/menu/menu-positions.ts) | "after" | Custom choice for opening the menu at the bottom. Can be `before` or `after`. |
@@ -32,5 +34,3 @@ Shows user information.
The component shows a round icon for the user and will show extra information about
the user when clicked.
If user is logged in with both ACS and APS, the ACS image will be shown.
In case of SSO authentication, the information related to the user like firstname, lastname will be fetched using the Keycloak [`Api`](../../../lib/testing/src/lib/core/structure/api.ts)

View File

@@ -0,0 +1,38 @@
---
Title: Process User Info component
Added: v6.0.0
Status: Active
Last reviewed: 2023-01-24
---
# [Process User Info component](../../../lib/process-services/src/lib/process-user-info/process-user-info.component.ts "Defined in process-user-info.component.ts")
Shows user information for `PROCESS` and `ALL` mode.
## Basic usage
```html
<adf-process-user-info></adf-process-user-info>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| isLoggedIn | `boolean` | Is user logged in |
| bpmUser | `BpmUserModel` | Bpm user model. |
| ecmUser | `EpmUserModel` | Ecm user model. |
| mode | `UserInfoMode` | `UserInfoMode.PROCESS` | current mode. |
| bpmBackgroundImage | `string` | | Custom path for the background banner image for APS users. |
| BpmBackgroundImage | `string` | | Custom path for the background banner image for ACS users. |
| menuPositionX | [`MenuPositionX`](https://github.com/angular/components/blob/master/src/material/menu/menu-positions.ts) | "after" | Custom choice for opening the menu at the bottom. Can be `before` or `after`. |
| menuPositionY | [`MenuPositionY`](https://github.com/angular/components/blob/master/src/material/menu/menu-positions.ts) | "below" | Custom choice for opening the menu at the bottom. Can be `above` or `below`. |
| namePosition | `string` | "right" | When the username is shown, this defines its position relative to the user info button. Can be `right` or `left`. |
| showName | `boolean` | true | Shows/hides the username next to the user info button. |
## Details
The component shows a round icon for the user and will show extra information about
the user when clicked.

View File

@@ -86,6 +86,7 @@ How to fix it:
| Class | Before | After |
| --- | -- | --- |
| `LoginDialogService` | `@alfresco/adf-core`|
| `UserInfoComponent` | `@alfresco/adf-core`|
### DataColumnModule
@@ -225,6 +226,16 @@ The ```adf-comments``` has now two specialization in :
From v.6.0.0 and after [`ViewerComponent`](../../docs/core/components/viewer.component.md) no longer show content from ACS, so instead of taking `nodeId` as `@Input`, it takes `blobFile` and `urlFile`. For more details check the [`PR`](https://github.com/Alfresco/alfresco-ng2-components/pull/7992).
If you need to display content from ACS you can use instead the new [`AlfrescoViewerComponent`](../../docs/content-services/components/alfresco-viewer.component.md)
### UserInfoComponent
From v.6.0.0 and after ```UserInfoComponent``` is no longer active.
In its place there are now 3 presentational components:
- [`IdentityUserInfoComponent`](../../docs/core/components/identity-user-info.component.md) present in core
- [`ContentUserInfoComponent`](../../docs/content-services/components/content-user-info.component.md) present in content-services
- [`ProcessUserInfoComponent`](../../docs/process-services/components/process-user-info.component.md) present in process-services
To build a similar logic to the one in ```UserInfoComponent``` check implementation on [`demo-shell`](../../demo-shell/src/app/components/app-layout/user-info/user-info.component.ts)
## Renamed items
### New Classes or Services

View File

@@ -0,0 +1,111 @@
<div
id="userinfo_container"
[class.adf-userinfo-name-right]="showOnRight"
(keyup)="onKeyPress($event)"
class="adf-userinfo-container"
*ngIf="canShow"
>
<span *ngIf="showName" id="adf-userinfo-ecm-name-display" class="adf-userinfo-name">
{{ecmUser | fullName}}
</span>
<button mat-button [matMenuTriggerFor]="menu" class="adf-userinfo-menu_button"
data-automation-id="adf-user-profile">
<div class="adf-userinfo-button-profile" id="user-profile">
<div *ngIf="identityUser; else showEcmUserImage" id="identity-user-image">
<div *ngIf="ecmUser?.avatarId; else initialTemplate">
<div class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getEcmAvatar(ecmUser.avatarId)" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
</div>
<ng-template #initialTemplate>
<div [innerHTML]="identityUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
<ng-template #showEcmUserImage>
<div id="ecm-user-image">
<div *ngIf="ecmUser.avatarId; else initialTemplate" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getEcmAvatar(ecmUser.avatarId)" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
<ng-template #initialTemplate>
<div [outerHTML]="ecmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
</ng-template>
</div>
</button>
<mat-menu #menu="matMenu" id="user-profile-lists" [xPosition]="menuPositionX" [yPosition]="menuPositionY"
[overlapTrigger]="false" class="adf-userinfo-menu">
<mat-tab-group id="tab-group-env" (click)="stopClosing($event)" selectedIndex="0" role="menuitem"
class="adf-userinfo-tab adf-hide-tab">
<mat-tab label="{{ 'USER_PROFILE.TAB.CS' | translate }}" role="dialog"
*ngIf="mode === userInfoMode.CONTENT">
<mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header"
[style.background-image]="'url(' + ecmBackgroundImage + ')'">
<div *ngIf="ecmUser.avatarId; else initialTemplate"
class="adf-userinfo-profile-container adf-hide-small">
<img class="adf-userinfo-profile-picture" id="ecm-user-detail-image"
alt="ecm-profile-image" [src]="getEcmAvatar(ecmUser.avatarId)"/>
</div>
<ng-template #initialTemplate>
<div
[outerHTML]="ecmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="ecm-username">{{ecmUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="ecm-full-name"
class="adf-userinfo__detail-title">{{ecmUser | fullName}}</span>
<span class="adf-userinfo__detail-profile" id="ecm-email"> {{ecmUser.email}} </span>
<a class="adf-userinfo__detail-profile" href="#/profile">
{{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }}</a>
</div>
<div class="adf-userinfo-detail">
<span class="adf-userinfo__secondary-info" id="ecm-job-title-label">
{{ 'USER_PROFILE.LABELS.ECM.JOB_TITLE' | translate }}
<span id="ecm-job-title"
class="adf-userinfo__detail-profile"> {{ ecmUser.jobTitle ? ecmUser.jobTitle : 'N/A' }} </span>
</span>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
<mat-tab id="identity-panel" role="dialog" *ngIf="mode === userInfoMode.CONTENT_SSO">
<mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header"
[style.background-image]="'url(' + bpmBackgroundImage + ')'">
<div *ngIf="ecmUser.avatarId; else initialTemplate"
class="adf-userinfo-profile-container adf-hide-small">
<img class="adf-userinfo-profile-picture" id="ecm-user-detail-image"
alt="ecm-profile-image" [src]="getEcmAvatar(ecmUser.avatarId)"/>
</div>
<ng-template #initialTemplate>
<div
[outerHTML]="identityUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="identity-username">{{identityUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="identity-full-name"
class="adf-userinfo__detail-title">{{identityUser | fullName}}</span>
<span class="adf-userinfo__detail-profile"
id="identity-email"> {{identityUser.email}} </span>
<a class="adf-userinfo__detail-profile" href="#/profile">
{{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }}</a>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
</mat-tab-group>
</mat-menu>
</div>

View File

@@ -0,0 +1,272 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CoreTestingModule,
fakeEcmEditedUser,
fakeEcmUser,
fakeEcmUserNoImage,
IdentityUserModel,
InitialUsernamePipe,
setupTestBed,
UserInfoMode
} from '@alfresco/adf-core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatMenuModule } from '@angular/material/menu';
import { By, DomSanitizer } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { ContentTestingModule } from '../testing/content.testing.module';
import { ContentUserInfoComponent } from './content-user-info.component';
class FakeSanitizer extends DomSanitizer {
constructor() {
super();
}
sanitize(html) {
return html;
}
bypassSecurityTrustHtml(value: string): any {
return value;
}
bypassSecurityTrustStyle(): any {
return null;
}
bypassSecurityTrustScript(): any {
return null;
}
bypassSecurityTrustUrl(): any {
return null;
}
bypassSecurityTrustResourceUrl(): any {
return null;
}
}
describe('ContentUserInfoComponent', () => {
const profilePictureUrl = 'alfresco-logo.svg';
let component: ContentUserInfoComponent;
let fixture: ComponentFixture<ContentUserInfoComponent>;
let element: HTMLElement;
const identityUserMock = { firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
const identityUserWithOutLastNameMock = { firstName: 'fake-identity-first-name', lastName: null, email: 'fakeIdentity@email.com' };
const openUserInfo = () => {
fixture.detectChanges();
const imageButton = element.querySelector<HTMLButtonElement>('#logged-user-img');
imageButton.click();
fixture.detectChanges();
};
const whenFixtureReady = async () => {
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule,
ContentTestingModule,
MatMenuModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(ContentUserInfoComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
spyOn(window, 'requestAnimationFrame').and.returnValue(1);
});
afterEach(() => {
fixture.destroy();
});
it('should not show any image if the user is not logged in', () => {
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#logged-user-img')).toBeNull();
});
it('should NOT have users immediately after ngOnInit', () => {
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#ecm_username')).toBeNull();
expect(element.querySelector('#user-profile-lists')).toBeNull();
});
describe('when user is logged on ecm', () => {
beforeEach(() => {
component.ecmUser = fakeEcmUser as any;
component.isLoggedIn = true;
});
describe('ui', () => {
it('should show ecm only last name when user first name is null ', async () => {
component.ecmUser = fakeEcmEditedUser as any;
await whenFixtureReady();
openUserInfo();
expect(element.querySelector('#userinfo_container')).toBeDefined();
const ecmUsername = fixture.debugElement.query(By.css('#ecm-username'));
expect(ecmUsername).toBeDefined();
expect(ecmUsername).not.toBeNull();
expect(ecmUsername.nativeElement.textContent).not.toContain('fake-ecm-first-name');
expect(ecmUsername.nativeElement.textContent).not.toContain('null');
});
it('should show the username when showName attribute is true', async () => {
await whenFixtureReady();
expect(component.showName).toBeTruthy();
expect(element.querySelector('#adf-userinfo-ecm-name-display')).not.toBeNull();
});
it('should hide the username when showName attribute is false', async () => {
component.showName = false;
await whenFixtureReady();
expect(element.querySelector('#adf-userinfo-ecm-name-display')).toBeNull();
});
it('should have the defined class to show the name on the right side', async () => {
await whenFixtureReady();
expect(element.querySelector('#userinfo_container').classList).toContain('adf-userinfo-name-right');
});
it('should not have the defined class to show the name on the left side', async () => {
component.namePosition = 'left';
await whenFixtureReady();
expect(element.querySelector('#userinfo_container').classList).not.toContain('adf-userinfo-name-right');
});
describe('and has image', () => {
beforeEach(async () => {
component.ecmUser = fakeEcmUser as any;
component.isLoggedIn = true;
spyOn(component, 'getEcmAvatar').and.returnValue(profilePictureUrl);
await whenFixtureReady();
});
it('should get the ecm current user image', async () => {
openUserInfo();
const loggedImage = fixture.debugElement.query(By.css('#logged-user-img'));
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(loggedImage).not.toBeNull();
expect(loggedImage.properties.src).toContain(profilePictureUrl);
});
it('should display the current user image if user has avatarId', () => {
openUserInfo();
const loggedImage = fixture.debugElement.query(By.css('#logged-user-img'));
expect(component.ecmUser).toBeDefined();
expect(component.ecmUser.avatarId).toBe('fake-avatar-id');
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(loggedImage).not.toBeNull();
expect(loggedImage.properties.src).toContain(profilePictureUrl);
});
it('should get the ecm user information', async () => {
openUserInfo();
const ecmImage = fixture.debugElement.query(By.css('#ecm-user-detail-image'));
const ecmFullName = fixture.debugElement.query(By.css('#ecm-full-name'));
const ecmJobTitle = fixture.debugElement.query(By.css('#ecm-job-title-label'));
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(fixture.debugElement.query(By.css('#ecm-username'))).not.toBeNull();
expect(ecmImage).not.toBeNull();
expect(ecmImage.properties.src).toContain(profilePictureUrl);
expect(ecmFullName.nativeElement.textContent).toContain('fake-ecm-first-name fake-ecm-last-name');
expect(ecmJobTitle.nativeElement.textContent).toContain('USER_PROFILE.LABELS.ECM.JOB_TITLE');
});
});
describe('and has no image', () => {
beforeEach( async () => {
component.ecmUser = fakeEcmUserNoImage as any;
component.isLoggedIn = true;
await whenFixtureReady();
});
it('should show N/A when the job title is null', () => {
const imageButton = element.querySelector<HTMLButtonElement>('[data-automation-id="user-initials-image"]');
imageButton.click();
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).not.toBeNull();
const ecmJobTitle = fixture.debugElement.query(By.css('#ecm-job-title'));
expect(ecmJobTitle).not.toBeNull();
expect(ecmJobTitle).not.toBeNull();
expect(ecmJobTitle.nativeElement.textContent).toContain('N/A');
});
it('should not show the tabs', () => {
const imageButton = element.querySelector<HTMLButtonElement>('[data-automation-id="user-initials-image"]');
imageButton.click();
fixture.detectChanges();
const tabHeader = fixture.debugElement.query(By.css('#tab-group-env'));
expect(tabHeader.classes['adf-hide-tab']).toBeTruthy();
});
it('should display the current user Initials if the user dose not have avatarId', () => {
fixture.detectChanges();
const pipe = new InitialUsernamePipe(new FakeSanitizer());
const expected = pipe.transform({
id: 13,
firstName: 'Wilbur',
lastName: 'Adams',
email: 'wilbur@app.com'
});
expect(expected).toBe('<div data-automation-id="user-initials-image" class="">WA</div>');
expect(component.ecmUser).toBeDefined();
expect(component.ecmUser.avatarId).toBeNull();
});
});
});
describe('when identity user is logged in', () => {
beforeEach(() => {
component.ecmUser = fakeEcmUser as any;
component.identityUser = identityUserMock as unknown as IdentityUserModel;
component.isLoggedIn = true;
component.mode = UserInfoMode.CONTENT_SSO;
});
it('should not show initials if the user have avatar and provider is ECM', async () => {
component.identityUser = identityUserWithOutLastNameMock as unknown as IdentityUserModel;
await whenFixtureReady();
expect(element.querySelector('.adf-userinfo-pic')).toBeNull();
expect(element.querySelector('.adf-userinfo-profile-image')).toBeDefined();
expect(element.querySelector('.adf-userinfo-profile-image')).not.toBeNull();
});
});
});
});

View File

@@ -0,0 +1,108 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EcmUserModel, IdentityUserModel, PeopleContentService, UserInfoMode } from '@alfresco/adf-core';
import { Component, Input, OnDestroy, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { Subject } from 'rxjs';
@Component({
selector: 'adf-content-user-info',
templateUrl: './content-user-info.component.html',
styleUrls: ['./content-user-info.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ContentUserInfoComponent implements OnDestroy {
@ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
@Input()
isLoggedIn: boolean;
@Input()
ecmUser: EcmUserModel;
@Input()
identityUser: IdentityUserModel;
@Input()
mode: UserInfoMode = UserInfoMode.CONTENT;
/** Custom path for the background banner image for ACS users. */
@Input()
ecmBackgroundImage: string = './assets/images/ecm-background.png';
/** Custom path for the background banner image for APS users. */
@Input()
bpmBackgroundImage: string = './assets/images/bpm-background.png';
/** Custom choice for opening the menu at the bottom. Can be `before` or `after`. */
@Input()
menuPositionX: MenuPositionX = 'after';
/** Custom choice for opening the menu at the bottom. Can be `above` or `below`. */
@Input()
menuPositionY: MenuPositionY = 'below';
/** Shows/hides the username next to the user info button. */
@Input()
showName: boolean = true;
/** When the username is shown, this defines its position relative to the user info button.
* Can be `right` or `left`.
*/
@Input()
namePosition: string = 'right';
userInfoMode = UserInfoMode;
private destroy$ = new Subject();
constructor(private peopleContentService: PeopleContentService) {
}
ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
onKeyPress(event: KeyboardEvent) {
this.closeUserModal(event);
}
private closeUserModal($event: KeyboardEvent) {
if ($event.keyCode === 27) {
this.trigger.closeMenu();
}
}
stopClosing(event: Event) {
event.stopPropagation();
}
getEcmAvatar(avatarId: string): string {
return this.peopleContentService.getUserProfileImage(avatarId);
}
get showOnRight(): boolean {
return this.namePosition === 'right';
}
get canShow(): boolean {
return this.isLoggedIn && !!this.ecmUser && !!this.mode;
}
}

View File

@@ -0,0 +1,41 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ContentUserInfoComponent } from './content-user-info.component';
import { TranslateModule } from '@ngx-translate/core';
import { PipeModule } from '@alfresco/adf-core';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatTabsModule } from '@angular/material/tabs';
import { MatCardModule } from '@angular/material/card';
@NgModule({
declarations: [ContentUserInfoComponent],
imports: [
CommonModule,
MatButtonModule,
MatMenuModule,
MatTabsModule,
MatCardModule,
TranslateModule,
PipeModule
],
exports: [ContentUserInfoComponent]
})
export class ContentUserInfoModule {}

View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './content-user-info.component';
export * from './content-user-info.module';

View File

@@ -48,6 +48,7 @@ import { ContentPipeModule } from './pipes/content-pipe.module';
import { NodeCommentsModule } from './node-comments/node-comments.module';
import { TreeModule } from './tree/tree.module';
import { AlfrescoViewerModule } from './viewer/alfresco-viewer.module';
import { ContentUserInfoModule } from './content-user-info/content-user-info.module';
@NgModule({
imports: [
@@ -62,6 +63,7 @@ import { AlfrescoViewerModule } from './viewer/alfresco-viewer.module';
DialogModule,
SearchModule,
DocumentListModule,
ContentUserInfoModule,
UploadModule,
MaterialModule,
SitesDropdownModule,
@@ -98,6 +100,7 @@ import { AlfrescoViewerModule } from './viewer/alfresco-viewer.module';
TagModule,
WebScriptModule,
DocumentListModule,
ContentUserInfoModule,
UploadModule,
SearchModule,
SitesDropdownModule,

View File

@@ -20,6 +20,7 @@ export * from './lib/social/index';
export * from './lib/tag/index';
export * from './lib/webscript/index';
export * from './lib/document-list/index';
export * from './lib/content-user-info/index';
export * from './lib/upload/index';
export * from './lib/search/index';
export * from './lib/site-dropdown/index';

View File

@@ -27,6 +27,7 @@ export * from './services/thumbnail.service';
export * from './services/sort-by-category.service';
export * from './models/log-levels.model';
export * from './models/user-info-mode.enum';
export * from './interface/search-component.interface';

View File

@@ -0,0 +1,24 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum UserInfoMode {
ALL = 'ALL',
CONTENT = 'CONTENT',
PROCESS = 'PROCESS',
SSO = 'SSO',
CONTENT_SSO = 'CONTENT_SSO'
}

View File

@@ -31,7 +31,6 @@ import { LanguageMenuModule } from './language-menu/language-menu.module';
import { LoginModule } from './login/login.module';
import { PaginationModule } from './pagination/pagination.module';
import { ToolbarModule } from './toolbar/toolbar.module';
import { UserInfoModule } from './userinfo/userinfo.module';
import { ViewerModule } from './viewer/viewer.module';
import { FormBaseModule } from './form/form-base.module';
import { SidenavLayoutModule } from './layout/layout.module';
@@ -64,6 +63,7 @@ import { RichTextEditorModule } from './rich-text-editor/rich-text-editor.module
import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthenticationService } from './auth/services/authentication.service';
import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
import { IdentityUserInfoModule } from './identity-user-info/identity-user-info.module';
@NgModule({
imports: [
@@ -74,11 +74,11 @@ import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
SidenavLayoutModule,
PipeModule,
CommonModule,
IdentityUserInfoModule,
DirectiveModule,
DownloadZipDialogModule,
FormsModule,
ReactiveFormsModule,
UserInfoModule,
MaterialModule,
AppConfigModule,
PaginationModule,
@@ -118,8 +118,8 @@ import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
DownloadZipDialogModule,
ClipboardModule,
FormsModule,
IdentityUserInfoModule,
ReactiveFormsModule,
UserInfoModule,
MaterialModule,
AppConfigModule,
PaginationModule,

View File

@@ -0,0 +1,45 @@
<div
id="userinfo_container"
[class.adf-userinfo-name-right]="showOnRight"
(keyup)="onKeyPress($event)"
class="adf-userinfo-container"
*ngIf="isLoggedIn"
>
<span *ngIf="showName" id="adf-userinfo-identity-name-display" class="adf-userinfo-name">
{{identityUser | fullName}}
</span>
<button mat-button [matMenuTriggerFor]="menu" class="adf-userinfo-menu_button"
data-automation-id="adf-user-profile">
<div class="adf-userinfo-button-profile" id="user-profile">
<div id="identity-user-image">
<div [innerHTML]="identityUser | usernameInitials:'adf-userinfo-pic'"></div>
</div>
</div>
</button>
<mat-menu #menu="matMenu" id="user-profile-lists" [xPosition]="menuPositionX" [yPosition]="menuPositionY"
[overlapTrigger]="false" class="adf-userinfo-menu">
<mat-tab-group id="tab-group-env" (click)="stopClosing($event)" selectedIndex="0" role="menuitem"
class="adf-userinfo-tab adf-hide-tab">
<mat-tab id="identity-panel" role="dialog">
<mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header"
[style.background-image]="'url(' + bpmBackgroundImage + ')'">
<div class="adf-userinfo-title" id="identity-username">{{identityUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="identity-full-name"
class="adf-userinfo__detail-title">{{identityUser | fullName}}</span>
<span class="adf-userinfo__detail-profile"
id="identity-email"> {{identityUser.email}} </span>
<a class="adf-userinfo__detail-profile" href="#/profile">
{{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }}</a>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
</mat-tab-group>
</mat-menu>
</div>

View File

@@ -0,0 +1,188 @@
.adf {
&-userinfo-container {
display: flex;
align-items: center;
padding: 0 5px;
}
&-userinfo-name-right {
flex-direction: row-reverse;
}
&-userinfo-name {
padding: 0 5px;
@media screen and (max-width: 959px) {
display: none;
}
}
&-userinfo-pic {
background: var(--adf-user-info-color);
display: inline-block;
width: 40px;
height: 40px;
border-radius: 100px;
text-align: center;
font-weight: bolder;
font-size: var(--theme-adf-picture-1-font-size);
text-transform: uppercase;
vertical-align: middle;
line-height: 40px;
}
&-userinfo-profile-image {
background: var(--adf-user-info-color);
text-align: center;
border-radius: 90%;
width: 40px;
height: 40px;
margin-right: 0;
cursor: pointer;
vertical-align: middle;
margin-left: 0;
}
&-userinfo-profile-container {
display: inline-block;
}
&-userinfo-menu_button.mat-button {
margin-right: 0;
border-radius: 90%;
padding: 0;
min-width: 40px;
height: 40px;
}
&-userinfo-tab .mat-tab-header {
align-self: center;
width: 100%;
min-width: 250px;
}
&-userinfo-tab .mat-tab-label {
flex: auto;
font-weight: 500;
font-size: var(--theme-body-1-font-size);
text-transform: uppercase;
line-height: 48px;
text-align: center;
}
&-userinfo-card-header {
align-items: center;
display: flex;
justify-content: stretch;
line-height: normal;
height: 100px;
box-sizing: border-box;
}
&-userinfo-card.mat-card {
padding: 0;
}
&-userinfo-supporting-text {
font-size: var(--theme-body-1-font-size);
font-weight: 400;
letter-spacing: 0;
line-height: 18px;
overflow: hidden;
padding: 32px;
column-count: 2;
display: flex;
justify-content: space-between;
@media screen and (max-width: 599px) {
padding: 10px;
}
}
&-userinfo-title {
font-size: var(--theme-title-font-size);
}
&-userinfo__detail-profile {
align-items: flex-start;
font-size: var(--theme-body-1-font-size);
font-weight: 400;
letter-spacing: 0;
line-height: 18px;
display: block;
padding: 0;
margin: 0;
}
&-userinfo__detail-title {
text-overflow: ellipsis;
font-size: var(--theme-subheading-2-font-size);
font-weight: 400;
letter-spacing: 0.04em;
line-height: 20px;
align-items: flex-start;
}
&-userinfo__secondary-info {
font-size: var(--theme-body-1-font-size);
font-weight: 400;
letter-spacing: 0;
line-height: 18px;
align-items: flex-end;
}
&-userinfo-profile-picture {
background: var(--adf-user-info-color);
background-size: cover;
border-radius: 50%;
height: 80px;
width: 80px;
margin-left: 0;
margin-right: 8px;
}
&-userinfo-profile-initials {
text-transform: uppercase;
background-size: cover;
background-color: var(--adf-user-info-color);
border-radius: 50%;
height: 80px;
width: 80px;
margin-left: 0;
margin-right: 8px;
font-size: 35px;
font-weight: 400;
letter-spacing: 0;
line-height: 78px;
overflow: hidden;
display: flex;
justify-content: space-around;
}
&-userinfo-button-profile {
display: inline-block;
border: 0;
vertical-align: middle;
}
&-userinfo-detail {
text-align: left;
}
&-hide-tab .mat-tab-label-active {
display: none !important;
}
}
@media only screen and (min-device-width: 480px) {
.mat-menu-panel.adf-userinfo-menu {
max-height: 450px;
min-width: 450px;
overflow: auto;
padding: 0;
}
}
.mat-menu-panel.adf-userinfo-menu .mat-menu-content {
padding: 0;
}

View File

@@ -0,0 +1,139 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IdentityUserInfoComponent } from './identity-user-info.component';
import { setupTestBed } from '../testing/setup-test-bed';
import { TranslateModule } from '@ngx-translate/core';
import { CoreTestingModule } from '../testing/core.testing.module';
import { MatMenuModule } from '@angular/material/menu';
import { By } from '@angular/platform-browser';
import { IdentityUserModel } from '../auth/models/identity-user.model';
describe('IdentityUserInfoComponent', () => {
let component: IdentityUserInfoComponent;
let fixture: ComponentFixture<IdentityUserInfoComponent>;
let element: HTMLElement;
const identityUserMock = { firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' } as unknown as IdentityUserModel;
const identityUserWithOutFirstNameMock = { firstName: null, lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' } as unknown as IdentityUserModel;
const identityUserWithOutLastNameMock = { firstName: 'fake-identity-first-name', lastName: null, email: 'fakeIdentity@email.com' } as unknown as IdentityUserModel;
const whenFixtureReady = async () => {
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule,
MatMenuModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(IdentityUserInfoComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
spyOn(window, 'requestAnimationFrame').and.returnValue(1);
});
afterEach(() => {
fixture.destroy();
});
it('should not show any image if the user is not logged in', () => {
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#logged-user-img')).toBeNull();
});
it('should NOT have users immediately after ngOnInit', () => {
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#ecm_username')).toBeNull();
expect(element.querySelector('#bpm_username')).toBeNull();
expect(element.querySelector('#user-profile-lists')).toBeNull();
});
describe('when identity user is logged in', () => {
beforeEach(() => {
component.identityUser = identityUserMock;
component.isLoggedIn = true;
});
it('should show the identity user initials', async () => {
await whenFixtureReady();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('[data-automation-id="user-initials-image"]')?.textContent).toContain('ff');
});
it('should show full name next to the user image', async () => {
await whenFixtureReady();
const imageButton = element.querySelector<HTMLButtonElement>('#identity-user-image');
imageButton?.click();
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).not.toBeNull();
const identityUserName = fixture.debugElement.query(By.css('#identity-username'));
expect(identityUserName).toBeDefined();
expect(identityUserName).not.toBeNull();
expect(identityUserName.nativeElement.textContent).toContain('fake-identity-first-name fake-identity-last-name');
});
it('should show last name if first name is null', async () => {
component.identityUser = identityUserWithOutFirstNameMock;
await whenFixtureReady();
const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display');
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#adf-userinfo-identity-name-display')).not.toBeNull();
expect(fullNameElement?.textContent).toContain('fake-identity-last-name');
expect(fullNameElement?.textContent).not.toContain('fake-identity-first-name');
});
it('should not show first name if it is null string', async () => {
component.identityUser = identityUserWithOutFirstNameMock;
await whenFixtureReady();
const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display');
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(fullNameElement).toBeDefined();
expect(fullNameElement?.textContent).toContain('fake-identity-last-name');
expect(fullNameElement?.textContent).not.toContain('null');
});
it('should not show last name if it is null string', async () => {
component.identityUser = identityUserWithOutLastNameMock;
await whenFixtureReady();
const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display');
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(fullNameElement).toBeDefined();
expect(fullNameElement?.textContent).toContain('fake-identity-first-name');
expect(fullNameElement?.textContent).not.toContain('null');
});
});
});

View File

@@ -0,0 +1,128 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Meta,
moduleMetadata,
Story
} from '@storybook/angular';
import { CoreStoryModule } from '../testing/core.story.module';
import { IdentityUserInfoComponent } from './identity-user-info.component';
import { IdentityUserInfoModule } from './identity-user-info.module';
const fakeIdentityUser = {
familyName: 'Identity',
givenName: 'John',
email: 'john.identity@gmail.com',
username: 'johnyIdentity99'
};
export default {
component: IdentityUserInfoComponent,
title: 'Core/Identity User Info/Identity User Info',
decorators: [
moduleMetadata({
imports: [CoreStoryModule, IdentityUserInfoModule]
})
],
argTypes: {
isLoggedIn: {
description:
'Determines if user is logged in',
control: 'boolean',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: true }
}
},
identityUser: {
description:
'Identity User Info',
control: 'object',
table: {
type: { summary: 'IdentityUserModel' }
}
},
menuPositionX: {
description:
'Material Angular menu horizontal position in regard to User Info',
control: 'radio',
options: ['before', 'after'],
defaultValue: 'after',
table: {
type: { summary: 'MenuPositionX' },
defaultValue: { summary: 'after' }
}
},
menuPositionY: {
description:
'Material Angular menu vertical position in regard to User Info',
control: 'radio',
options: ['above', 'below'],
defaultValue: 'below',
table: {
type: { summary: 'MenuPositionY' },
defaultValue: { summary: 'below' }
}
},
showName: {
description:
'Determines if name should be shown next to user avatar',
control: 'boolean',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: true }
}
},
namePosition: {
description: 'User name position in regard to avatar',
control: 'radio',
options: ['left', 'right'],
defaultValue: 'right',
table: {
type: { summary: 'string' },
defaultValue: { summary: 'right' }
}
},
bpmBackgroundImage: {
description: 'Menu background banner image for APS users',
control: {
disable: true
},
table: {
type: {
summary: 'string'
},
defaultValue: {
summary: './assets/images/bpm-background.png'
}
}
}
}
} as Meta;
const template: Story<IdentityUserInfoComponent> = (args: IdentityUserInfoComponent) => ({
props: args
});
export const loginWithSSO = template.bind({});
loginWithSSO.args = {
identityUser: fakeIdentityUser
};
loginWithSSO.parameters = { layout: 'centered' };

View File

@@ -0,0 +1,89 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Input, OnDestroy, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { IdentityUserModel } from '../auth/models/identity-user.model';
import { Subject } from 'rxjs';
@Component({
selector: 'adf-identity-user-info',
templateUrl: './identity-user-info.component.html',
styleUrls: ['./identity-user-info.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class IdentityUserInfoComponent implements OnDestroy {
@ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
@Input()
isLoggedIn: boolean;
@Input()
identityUser: IdentityUserModel;
/** Custom path for the background banner image for APS users. */
@Input()
bpmBackgroundImage: string = './assets/images/bpm-background.png';
/** Custom choice for opening the menu at the bottom. Can be `before` or `after`. */
@Input()
menuPositionX: MenuPositionX = 'after';
/** Custom choice for opening the menu at the bottom. Can be `above` or `below`. */
@Input()
menuPositionY: MenuPositionY = 'below';
/** Shows/hides the username next to the user info button. */
@Input()
showName: boolean = true;
/** When the username is shown, this defines its position relative to the user info button.
* Can be `right` or `left`.
*/
@Input()
namePosition: string = 'right';
private destroy$ = new Subject();
ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
onKeyPress(event: KeyboardEvent) {
this.closeUserModal(event);
}
private closeUserModal($event: KeyboardEvent) {
if ($event.keyCode === 27) {
this.trigger.closeMenu();
}
}
stopClosing(event: Event) {
event.stopPropagation();
}
get showOnRight(): boolean {
return this.namePosition === 'right';
}
get canShow(): boolean {
return this.isLoggedIn && !!this.identityUser;
}
}

View File

@@ -15,17 +15,18 @@
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { PipeModule } from '../pipes/pipe.module';
import { UserInfoComponent } from './components/user-info.component';
import { CommonModule } from '@angular/common';
import { IdentityUserInfoComponent } from './identity-user-info.component';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatMenuModule } from '@angular/material/menu';
import { MatTabsModule } from '@angular/material/tabs';
import { MatCardModule } from '@angular/material/card';
import { TranslateModule } from '@ngx-translate/core';
import { PipeModule } from '../pipes/pipe.module';
@NgModule({
declarations: [IdentityUserInfoComponent],
imports: [
CommonModule,
MatButtonModule,
@@ -35,12 +36,6 @@ import { MatCardModule } from '@angular/material/card';
TranslateModule,
PipeModule
],
declarations: [
UserInfoComponent
],
exports: [
UserInfoComponent
]
exports: [IdentityUserInfoComponent]
})
export class UserInfoModule {
}
export class IdentityUserInfoModule {}

View File

@@ -15,5 +15,4 @@
* limitations under the License.
*/
export * from './components/user-info.component';
export * from './userinfo.module';
export * from './public-api';

View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './identity-user-info.component';
export * from './identity-user-info.module';

View File

@@ -1,76 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Inject, Injectable } from '@angular/core';
import { ReplaySubject } from 'rxjs';
import { AlfrescoApiService } from './../../../services/alfresco-api.service';
@Injectable({
providedIn: 'root'
})
export class AuthenticationServiceMock {
onLogin: ReplaySubject<any> = new ReplaySubject<any>(1);
onLogout: ReplaySubject<any> = new ReplaySubject<any>(1);
constructor(
private alfrescoApi: AlfrescoApiService,
@Inject('MODE') public loginMode: string
) {}
isLoggedIn(): boolean {
return true;
}
isLoggedInWith(provider: string): boolean {
if (provider === 'BPM') {
return this.isBpmLoggedIn();
} else if (provider === 'ECM') {
return this.isEcmLoggedIn();
} else {
return this.isLoggedIn();
}
}
isKerberosEnabled(): boolean {
return this.loginMode === 'all';
}
isOauth(): boolean {
return this.loginMode === 'default' || this.loginMode === 'defaultEcm';
}
isECMProvider(): boolean {
return this.alfrescoApi.getInstance().isEcmConfiguration();
}
isBPMProvider(): boolean {
return this.alfrescoApi.getInstance().isBpmConfiguration();
}
isALLProvider(): boolean {
return this.loginMode === 'all';
}
isEcmLoggedIn(): boolean {
return this.loginMode === 'ecm' || this.loginMode === 'defaultEcm';
}
isBpmLoggedIn(): boolean {
return this.loginMode === 'bpm';
}
}

View File

@@ -1,78 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
BpmUserModel,
EcmUserModel
} from './../../../models';
import {
IdentityUserModel
} from './../../../auth/models/identity-user.model';
import { of } from 'rxjs';
export class PeopleContentServiceMock {
private fakeEcmUser = new EcmUserModel({
firstName: 'John',
lastName: 'Ecm',
avatarId: 'fake-avatar-id',
email: 'john.ecm@gmail.com',
jobTitle: 'Product Manager'
});
getCurrentUserInfo = () => of(this.fakeEcmUser);
getUserProfileImage = () => './assets/images/alfresco-logo.svg';
}
export class EcmUserServiceMock {
private fakeEcmUser = new EcmUserModel({
firstName: 'John',
lastName: 'Ecm',
avatarId: 'fake-avatar-id',
email: 'john.ecm@gmail.com',
jobTitle: 'Product Manager'
});
getCurrentUserInfo = () => of(this.fakeEcmUser);
getUserProfileImage = () => './assets/images/alfresco-logo.svg';
}
export class BpmUserServiceMock {
private fakeBpmUser = new BpmUserModel({
email: 'john.bpm@gmail.com',
firstName: 'John',
lastName: 'Bpm',
pictureId: 12,
tenantName: 'Name of Tenant'
});
getCurrentUserInfo = () => of(this.fakeBpmUser);
getCurrentUserProfileImage = () => './assets/images/alfresco-logo.svg';
}
export class IdentityUserServiceMock {
private fakeIdentityUser = {
familyName: 'Identity',
givenName: 'John',
email: 'john.identity@gmail.com',
username: 'johnyIdentity99'
};
getCurrentUserInfo = (): IdentityUserModel => this.fakeIdentityUser;
}

View File

@@ -1,162 +0,0 @@
<div id="userinfo_container" [class.adf-userinfo-name-right]="showOnRight" (keyup)="onKeyPress($event)"
class="adf-userinfo-container" *ngIf="isLoggedIn">
<ng-container *ngIf="showName">
<span *ngIf="identityUser$ | async as identityUser; else showBpmAndEcmUserFullNames"
id="adf-userinfo-identity-name-display"
class="adf-userinfo-name">{{identityUser | fullName}}</span>
<ng-template #showBpmAndEcmUserFullNames>
<span *ngIf="ecmUser$ | async as ecmUser; else showBpmUserFullName" id="adf-userinfo-ecm-name-display"
class="adf-userinfo-name">{{ecmUser | fullName}}</span>
<ng-template #showBpmUserFullName>
<span *ngIf="bpmUser$ | async as bpmUser" id="adf-userinfo-bpm-name-display"
class="adf-userinfo-name">{{bpmUser | fullName}}</span>
</ng-template>
</ng-template>
</ng-container>
<button mat-button [matMenuTriggerFor]="menu" class="adf-userinfo-menu_button"
data-automation-id="adf-user-profile">
<div class="adf-userinfo-button-profile" id="user-profile">
<div *ngIf="identityUser$ | async as identityUser; else showBpmAndEcmUserImage" id="identity-user-image">
<div *ngIf="(ecmUser$ | async)?.avatarId as avatarId; else initialTemplate">
<div class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getEcmAvatar(avatarId)" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
</div>
<ng-template #initialTemplate>
<div [innerHTML]="identityUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
<ng-template #showBpmAndEcmUserImage>
<div *ngIf="ecmUser$ | async as ecmUser; else showBpmUserImage" id="ecm-user-image">
<div *ngIf="ecmUser.avatarId; else initialTemplate" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getEcmAvatar(ecmUser.avatarId)" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
<ng-template #initialTemplate>
<div [outerHTML]="ecmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
<ng-template #showBpmUserImage>
<div *ngIf="bpmUser$ | async as bpmUser" id="bpm-user-image">
<div *ngIf="bpmUser.pictureId; else initialTemplate" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getBpmUserImage()" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
<ng-template #initialTemplate>
<div [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</button>
<mat-menu #menu="matMenu" id="user-profile-lists" [xPosition]="menuPositionX" [yPosition]="menuPositionY"
[overlapTrigger]="false" class="adf-userinfo-menu">
<mat-tab-group id="tab-group-env" (click)="stopClosing($event)" selectedIndex="0" role="menuitem"
class="adf-userinfo-tab" [class.adf-hide-tab]="!(bpmUser$ | async) || !(ecmUser$ | async)">
<mat-tab id="ecm-panel" label="{{ 'USER_PROFILE.TAB.CS' | translate }}" role="dialog"
*ngIf="mode==='CONTENT' || mode==='ALL'">
<mat-card class="adf-userinfo-card" *ngIf="ecmUser$ | async as ecmUser">
<mat-card-header class="adf-userinfo-card-header"
[style.background-image]="'url(' + ecmBackgroundImage + ')'">
<div *ngIf="ecmUser.avatarId; else initialTemplate"
class="adf-userinfo-profile-container adf-hide-small">
<img class="adf-userinfo-profile-picture" id="ecm-user-detail-image"
alt="ecm-profile-image" [src]="getEcmAvatar(ecmUser.avatarId)"/>
</div>
<ng-template #initialTemplate>
<div
[outerHTML]="ecmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="ecm-username">{{ecmUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="ecm-full-name"
class="adf-userinfo__detail-title">{{ecmUser | fullName}}</span>
<span class="adf-userinfo__detail-profile" id="ecm-email"> {{ecmUser.email}} </span>
<a *ngIf="mode==='CONTENT'" class="adf-userinfo__detail-profile" href="#/profile">
{{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }}</a>
</div>
<div class="adf-userinfo-detail">
<span class="adf-userinfo__secondary-info" id="ecm-job-title-label">
{{ 'USER_PROFILE.LABELS.ECM.JOB_TITLE' | translate }}
<span id="ecm-job-title"
class="adf-userinfo__detail-profile"> {{ ecmUser.jobTitle ? ecmUser.jobTitle : 'N/A' }} </span>
</span>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
<mat-tab id="bpm-panel" label="{{ 'USER_PROFILE.TAB.PS' | translate }}" role="dialog"
*ngIf="mode==='PROCESS' || mode==='ALL'">
<mat-card class="adf-userinfo-card" *ngIf="bpmUser$ | async as bpmUser">
<mat-card-header class="adf-userinfo-card-header"
[style.background-image]="'url(' + bpmBackgroundImage + ')'">
<img *ngIf="bpmUser.pictureId; else initialTemplate"
class="adf-userinfo-profile-picture adf-hide-small" id="bpm-user-detail-image"
alt="bpm-profile-image" [src]="getBpmUserImage()"/>
<ng-template #initialTemplate>
<div
[outerHTML]="bpmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="bpm-username">{{bpmUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="bpm-full-name"
class="adf-userinfo__detail-title">{{ bpmUser | fullName }}</span>
<span class="adf-userinfo__detail-profile" id="bpm-email"> {{bpmUser.email}} </span>
</div>
<div class="adf-userinfo-detail">
<span id="bpm-tenant" class="adf-userinfo__secondary-info">
{{ 'USER_PROFILE.LABELS.BPM.TENANT' | translate }}
<span
class="adf-userinfo__detail-profile">{{ bpmUser.tenantName ? bpmUser.tenantName : '' }}</span>
</span>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
<mat-tab id="identity-panel" role="dialog" *ngIf="mode==='SSO'">
<mat-card class="adf-userinfo-card" *ngIf="identityUser$ | async as identityUser">
<mat-card-header class="adf-userinfo-card-header"
[style.background-image]="'url(' + bpmBackgroundImage + ')'">
<div *ngIf="ecmUser$ | async as ecmUser">
<div *ngIf="ecmUser.avatarId; else initialTemplate"
class="adf-userinfo-profile-container adf-hide-small">
<img class="adf-userinfo-profile-picture" id="ecm-user-detail-image"
alt="ecm-profile-image" [src]="getEcmAvatar(ecmUser.avatarId)"/>
</div>
</div>
<ng-template #initialTemplate>
<div
[outerHTML]="identityUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="identity-username">{{identityUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="identity-full-name"
class="adf-userinfo__detail-title">{{identityUser | fullName}}</span>
<span class="adf-userinfo__detail-profile"
id="identity-email"> {{identityUser.email}} </span>
<a class="adf-userinfo__detail-profile" href="#/profile">
{{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }}</a>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
</mat-tab-group>
</mat-menu>
</div>

View File

@@ -1,592 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By, DomSanitizer } from '@angular/platform-browser';
import { ContentService, PeopleContentService } from '../../services';
import { AuthenticationService } from '../../auth/services/authentication.service';
import { IdentityUserService } from '../../auth/services/identity-user.service';
import { InitialUsernamePipe } from '../../pipes';
import { fakeBpmUser } from '../../mock/bpm-user.service.mock';
import { fakeEcmEditedUser, fakeEcmUser, fakeEcmUserNoImage } from '../../mock/ecm-user.service.mock';
import { BpmUserService } from '../../services/bpm-user.service';
import { BpmUserModel } from '../../models/bpm-user.model';
import { EcmUserModel } from '../../models/ecm-user.model';
import { UserInfoComponent } from './user-info.component';
import { of } from 'rxjs';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { MatMenuModule } from '@angular/material/menu';
class FakeSanitizer extends DomSanitizer {
constructor() {
super();
}
sanitize(html) {
return html;
}
bypassSecurityTrustHtml(value: string): any {
return value;
}
bypassSecurityTrustStyle(): any {
return null;
}
bypassSecurityTrustScript(): any {
return null;
}
bypassSecurityTrustUrl(): any {
return null;
}
bypassSecurityTrustResourceUrl(): any {
return null;
}
}
describe('User info component', () => {
const profilePictureUrl = 'alfresco-logo.svg';
let component: UserInfoComponent;
let fixture: ComponentFixture<UserInfoComponent>;
let element: HTMLElement;
let authService: AuthenticationService;
let contentService: ContentService;
let peopleContentService: PeopleContentService;
let bpmUserService: BpmUserService;
let identityUserService: IdentityUserService;
const identityUserMock = { firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
const identityUserWithOutFirstNameMock = { firstName: null, lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
const identityUserWithOutLastNameMock = { firstName: 'fake-identity-first-name', lastName: null, email: 'fakeIdentity@email.com' };
const openUserInfo = () => {
fixture.detectChanges();
const imageButton = element.querySelector<HTMLButtonElement>('#logged-user-img');
imageButton.click();
fixture.detectChanges();
};
const whenFixtureReady = async () => {
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule,
MatMenuModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(UserInfoComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
authService = TestBed.inject(AuthenticationService);
peopleContentService = TestBed.inject(PeopleContentService);
bpmUserService = TestBed.inject(BpmUserService);
contentService = TestBed.inject(ContentService);
identityUserService = TestBed.inject(IdentityUserService);
spyOn(window, 'requestAnimationFrame').and.returnValue(1);
spyOn(bpmUserService, 'getCurrentUserProfileImage').and.returnValue(profilePictureUrl);
spyOn(contentService, 'getContentUrl').and.returnValue(profilePictureUrl);
});
afterEach(() => {
fixture.destroy();
});
it('should not show any image if the user is not logged in', () => {
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#logged-user-img')).toBeNull();
});
it('should NOT have users immediately after ngOnInit', () => {
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#ecm_username')).toBeNull();
expect(element.querySelector('#bpm_username')).toBeNull();
expect(element.querySelector('#user-profile-lists')).toBeNull();
});
describe('when user is logged on ecm', () => {
let getCurrenEcmtUserInfoStub;
let isOauthStub;
let isEcmLoggedInStub;
let isLoggedInStub;
let isBpmLoggedInStub;
beforeEach(() => {
isOauthStub = spyOn(authService, 'isOauth').and.returnValue(false);
isEcmLoggedInStub = spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
isLoggedInStub = spyOn(authService, 'isLoggedIn').and.returnValue(true);
isBpmLoggedInStub = spyOn(authService, 'isBpmLoggedIn').and.returnValue(false);
getCurrenEcmtUserInfoStub = spyOn(peopleContentService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmUser as any));
});
describe('ui ', () => {
it('should able to fetch ecm userInfo', (done) => {
component.getUserInfo();
fixture.detectChanges();
fixture.whenStable().then(() => {
component.ecmUser$.subscribe((response: EcmUserModel) => {
expect(response).toBeDefined();
expect(response.firstName).toBe('fake-ecm-first-name');
expect(response.lastName).toBe('fake-ecm-last-name');
expect(response.email).toBe('fakeEcm@ecmUser.com');
done();
});
});
});
it('should show ecm only last name when user first name is null ', async () => {
getCurrenEcmtUserInfoStub.and.returnValue(of(fakeEcmEditedUser));
await whenFixtureReady();
openUserInfo();
expect(element.querySelector('#userinfo_container')).toBeDefined();
const ecmUsername = fixture.debugElement.query(By.css('#ecm-username'));
expect(ecmUsername).toBeDefined();
expect(ecmUsername).not.toBeNull();
expect(ecmUsername.nativeElement.textContent).not.toContain('fake-ecm-first-name');
expect(ecmUsername.nativeElement.textContent).not.toContain('null');
});
it('should show the username when showName attribute is true', async () => {
await whenFixtureReady();
expect(component.showName).toBeTruthy();
expect(element.querySelector('#adf-userinfo-ecm-name-display')).not.toBeNull();
});
it('should hide the username when showName attribute is false', async () => {
component.showName = false;
await whenFixtureReady();
expect(element.querySelector('#adf-userinfo-ecm-name-display')).toBeNull();
});
it('should have the defined class to show the name on the right side', async () => {
await whenFixtureReady();
expect(element.querySelector('#userinfo_container').classList).toContain('adf-userinfo-name-right');
});
it('should not have the defined class to show the name on the left side', async () => {
component.namePosition = 'left';
await whenFixtureReady();
expect(element.querySelector('#userinfo_container').classList).not.toContain('adf-userinfo-name-right');
});
describe('and has image', () => {
beforeEach(async () => {
isOauthStub.and.returnValue(false);
isEcmLoggedInStub.and.returnValue(true);
isLoggedInStub.and.returnValue(true);
getCurrenEcmtUserInfoStub.and.returnValue(of(fakeEcmUser));
await whenFixtureReady();
});
it('should get the ecm current user image from the service', async () => {
openUserInfo();
const loggedImage = fixture.debugElement.query(By.css('#logged-user-img'));
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(loggedImage).not.toBeNull();
expect(loggedImage.properties.src).toContain(profilePictureUrl);
});
it('should display the current user image if user has avatarId', (done) => {
openUserInfo();
const loggedImage = fixture.debugElement.query(By.css('#logged-user-img'));
component.ecmUser$.subscribe((response: EcmUserModel) => {
expect(response).toBeDefined();
expect(response.avatarId).toBe('fake-avatar-id');
done();
});
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(loggedImage).not.toBeNull();
expect(loggedImage.properties.src).toContain(profilePictureUrl);
});
it('should get the ecm user information from the service', async () => {
openUserInfo();
const ecmImage = fixture.debugElement.query(By.css('#ecm-user-detail-image'));
const ecmFullName = fixture.debugElement.query(By.css('#ecm-full-name'));
const ecmJobTitle = fixture.debugElement.query(By.css('#ecm-job-title-label'));
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(fixture.debugElement.query(By.css('#ecm-username'))).not.toBeNull();
expect(ecmImage).not.toBeNull();
expect(ecmImage.properties.src).toContain(profilePictureUrl);
expect(ecmFullName.nativeElement.textContent).toContain('fake-ecm-first-name fake-ecm-last-name');
expect(ecmJobTitle.nativeElement.textContent).toContain('USER_PROFILE.LABELS.ECM.JOB_TITLE');
});
});
describe('and has no image', () => {
beforeEach( async () => {
isOauthStub.and.returnValue(false);
isEcmLoggedInStub.and.returnValue(true);
isLoggedInStub.and.returnValue(true);
isBpmLoggedInStub.and.returnValue(false);
getCurrenEcmtUserInfoStub.and.returnValue(of(fakeEcmUserNoImage));
await whenFixtureReady();
});
it('should show N/A when the job title is null', () => {
const imageButton = element.querySelector<HTMLButtonElement>('[data-automation-id="user-initials-image"]');
imageButton.click();
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).not.toBeNull();
const ecmJobTitle = fixture.debugElement.query(By.css('#ecm-job-title'));
expect(ecmJobTitle).not.toBeNull();
expect(ecmJobTitle).not.toBeNull();
expect(ecmJobTitle.nativeElement.textContent).toContain('N/A');
});
it('should not show the tabs', () => {
const imageButton = element.querySelector<HTMLButtonElement>('[data-automation-id="user-initials-image"]');
imageButton.click();
fixture.detectChanges();
const tabHeader = fixture.debugElement.query(By.css('#tab-group-env'));
expect(tabHeader.classes['adf-hide-tab']).toBeTruthy();
});
it('should display the current user Initials if the user dose not have avatarId', (done) => {
fixture.detectChanges();
const pipe = new InitialUsernamePipe(new FakeSanitizer());
const expected = pipe.transform({
id: 13,
firstName: 'Wilbur',
lastName: 'Adams',
email: 'wilbur@app.com'
});
expect(expected).toBe('<div data-automation-id="user-initials-image" class="">WA</div>');
component.ecmUser$.subscribe((response: EcmUserModel) => {
expect(response).toBeDefined();
expect(response.avatarId).toBeNull();
done();
});
});
});
});
describe('when user is logged on bpm', () => {
let getCurrentUserInfoStub: jasmine.Spy;
beforeEach(() => {
isOauthStub.and.returnValue(false);
isBpmLoggedInStub.and.returnValue(true);
isLoggedInStub.and.returnValue(true);
isEcmLoggedInStub.and.returnValue(false);
getCurrentUserInfoStub = spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser));
});
it('should fetch bpm userInfo', (done) => {
getCurrentUserInfoStub.and.returnValue(of(fakeBpmUser));
fixture.detectChanges();
fixture.whenStable().then(() => {
component.bpmUser$.subscribe((response: BpmUserModel) => {
expect(response).toBeDefined();
expect(response.firstName).toBe('fake-bpm-first-name');
expect(response.lastName).toBe('fake-bpm-last-name');
expect(response.email).toBe('fakeBpm@fake.com');
done();
});
});
});
it('should show full name next the user image', async () => {
await whenFixtureReady();
openUserInfo();
const bpmUserName = fixture.debugElement.query(By.css('#bpm-username'));
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(bpmUserName).toBeDefined();
expect(bpmUserName).not.toBeNull();
expect(bpmUserName.nativeElement.innerHTML).toContain('fake-bpm-first-name fake-bpm-last-name');
});
it('should get the bpm current user image from the service', async () => {
await whenFixtureReady();
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(element.querySelector('#logged-user-img')).not.toBeNull();
expect(element.querySelector('#logged-user-img').getAttribute('src')).toContain(profilePictureUrl);
});
it('should show last name if first name is null', async () => {
const wrongBpmUser: BpmUserModel = new BpmUserModel({
firstName: null,
lastName: 'fake-last-name'
});
getCurrentUserInfoStub.and.returnValue(of(wrongBpmUser));
await whenFixtureReady();
const fullNameElement = (element.querySelector('#adf-userinfo-bpm-name-display'));
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#adf-userinfo-bpm-name-display')).not.toBeNull();
expect(fullNameElement.textContent).toContain('fake-last-name');
expect(fullNameElement.textContent).not.toContain('fake-first-name');
});
it('should not show the tabs', async () => {
await whenFixtureReady();
openUserInfo();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#tab-group-env')).classes['adf-hide-tab']).toBeTruthy();
});
});
describe('when user is logged on bpm and ecm', () => {
beforeEach(() => {
isOauthStub.and.returnValue(false);
isEcmLoggedInStub.and.returnValue(true);
isBpmLoggedInStub.and.returnValue(true);
isLoggedInStub.and.returnValue(true);
getCurrenEcmtUserInfoStub.and.returnValue(of(fakeEcmUser));
spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser));
});
it('should get the bpm user information from the service', async () => {
await whenFixtureReady();
openUserInfo();
const bpmTab = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'))[1];
bpmTab.triggerEventHandler('click', null);
fixture.detectChanges();
await fixture.whenStable();
const bpmUsername = fixture.debugElement.query(By.css('#bpm-username'));
const bpmImage = fixture.debugElement.query(By.css('#bpm-user-detail-image'));
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(bpmUsername).not.toBeNull();
expect(bpmImage).not.toBeNull();
expect(bpmImage.properties.src).toContain(profilePictureUrl);
expect(bpmUsername.nativeElement.textContent).toContain('fake-bpm-first-name fake-bpm-last-name');
expect(fixture.debugElement.query(By.css('#bpm-tenant')).nativeElement.textContent).toContain('fake-tenant-name');
});
it('should get the ecm user information from the service', async () => {
await whenFixtureReady();
openUserInfo();
const ecmUsername = fixture.debugElement.query(By.css('#ecm-username'));
const ecmImage = fixture.debugElement.query(By.css('#ecm-user-detail-image'));
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(ecmUsername).not.toBeNull();
expect(ecmImage).not.toBeNull();
expect(ecmImage.properties.src).toContain(profilePictureUrl);
expect(fixture.debugElement.query(By.css('#ecm-full-name')).nativeElement.textContent).toContain('fake-ecm-first-name fake-ecm-last-name');
expect(fixture.debugElement.query(By.css('#ecm-job-title')).nativeElement.textContent).toContain('job-ecm-test');
});
it('should show the ecm image if exists', async () => {
await whenFixtureReady();
openUserInfo();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#logged-user-img')).toBeDefined();
expect(element.querySelector('#logged-user-img').getAttribute('src')).toEqual(profilePictureUrl);
});
it('should show the ecm initials if the ecm user has no image', async () => {
getCurrenEcmtUserInfoStub.and.returnValue(of(fakeEcmUserNoImage));
await whenFixtureReady();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('[data-automation-id="user-initials-image"]').textContent).toContain('ff');
});
it('should show the tabs for the env', async () => {
await whenFixtureReady();
openUserInfo();
const tabGroup = fixture.debugElement.query(By.css('#tab-group-env'));
const tabs = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'));
expect(tabGroup).not.toBeNull();
expect(tabGroup.classes['adf-hide-tab']).toBeFalsy();
expect(tabs.length).toBe(2);
});
it('should not close the menu when a tab is clicked', async () => {
await whenFixtureReady();
openUserInfo();
const tabGroup = fixture.debugElement.query(By.css('#tab-group-env'));
const tabs = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'));
expect(tabGroup).not.toBeNull();
tabs[1].triggerEventHandler('click', null);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#user-profile-lists'))).not.toBeNull();
});
});
describe('when identity user is logged in', () => {
let getCurrentUserInfoStub: jasmine.Spy;
beforeEach(() => {
isOauthStub.and.returnValue(true);
isLoggedInStub.and.returnValue(true);
isEcmLoggedInStub.and.returnValue(false);
getCurrentUserInfoStub = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(identityUserMock);
});
it('should show the identity user initials if is not ecm user', async () => {
await whenFixtureReady();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('[data-automation-id="user-initials-image"]').textContent).toContain('ff');
});
it('should able to fetch identity userInfo', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
component.identityUser$.subscribe(response => {
expect(response).toBeDefined();
expect(response.firstName).toBe('fake-identity-first-name');
expect(response.lastName).toBe('fake-identity-last-name');
expect(response.email).toBe('fakeIdentity@email.com');
done();
});
});
});
it('should show full name next the user image', async () => {
await whenFixtureReady();
const imageButton = element.querySelector<HTMLButtonElement>('#identity-user-image');
imageButton.click();
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).not.toBeNull();
const bpmUserName = fixture.debugElement.query(By.css('#identity-username'));
expect(bpmUserName).toBeDefined();
expect(bpmUserName).not.toBeNull();
expect(bpmUserName.nativeElement.textContent).toContain('fake-identity-first-name fake-identity-last-name');
});
it('should show last name if first name is null', async () => {
getCurrentUserInfoStub.and.returnValue(identityUserWithOutFirstNameMock);
await whenFixtureReady();
const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display');
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#adf-userinfo-identity-name-display')).not.toBeNull();
expect(fullNameElement.textContent).toContain('fake-identity-last-name');
expect(fullNameElement.textContent).not.toContain('fake-identity-first-name');
});
it('should not show first name if it is null string', async () => {
getCurrentUserInfoStub.and.returnValue(identityUserWithOutFirstNameMock);
await whenFixtureReady();
const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display');
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(fullNameElement).toBeDefined();
expect(fullNameElement.textContent).toContain('fake-identity-last-name');
expect(fullNameElement.textContent).not.toContain('null');
});
it('should not show last name if it is null string', async () => {
getCurrentUserInfoStub.and.returnValue(identityUserWithOutLastNameMock);
await whenFixtureReady();
const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display');
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(fullNameElement).toBeDefined();
expect(fullNameElement.textContent).toContain('fake-identity-first-name');
expect(fullNameElement.textContent).not.toContain('null');
});
it('should not show initials if the user have avatar and provider is ECM', async () => {
getCurrentUserInfoStub.and.returnValue(identityUserWithOutLastNameMock);
getCurrenEcmtUserInfoStub.and.returnValue(of(fakeEcmUser));
isEcmLoggedInStub.and.returnValue(true);
await whenFixtureReady();
expect(element.querySelector('.adf-userinfo-pic')).toBeNull();
expect(element.querySelector('.adf-userinfo-profile-image')).toBeDefined();
expect(element.querySelector('.adf-userinfo-profile-image')).not.toBeNull();
});
it('should show initials if the user has avatar but provider is not ECM', async () => {
getCurrentUserInfoStub.and.returnValue(identityUserWithOutLastNameMock);
getCurrenEcmtUserInfoStub.and.returnValue(of(fakeEcmUser));
isEcmLoggedInStub.and.returnValue(false);
await whenFixtureReady();
expect(element.querySelector('.adf-userinfo-pic')).not.toBeNull();
expect(element.querySelector('.adf-userinfo-profile-image')).toBeNull();
});
});
describe('kerberos', () => {
beforeEach(async () => {
isOauthStub.and.returnValue(false);
isEcmLoggedInStub.and.returnValue(false);
isBpmLoggedInStub.and.returnValue(false);
isLoggedInStub.and.returnValue(false);
spyOn(authService, 'isKerberosEnabled').and.returnValue(true);
spyOn(authService, 'isALLProvider').and.returnValue(true);
spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser));
getCurrenEcmtUserInfoStub.and.returnValue(of(fakeEcmUser));
await whenFixtureReady();
});
it('should show the bpm user information', async () => {
openUserInfo();
const bpmTab = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'))[1];
bpmTab.triggerEventHandler('click', null);
fixture.detectChanges();
await fixture.whenStable();
const bpmUsername = fixture.debugElement.query(By.css('#bpm-username'));
const bpmImage = fixture.debugElement.query(By.css('#bpm-user-detail-image'));
expect(bpmImage.properties.src).toContain(profilePictureUrl);
expect(bpmUsername.nativeElement.textContent).toContain('fake-bpm-first-name fake-bpm-last-name');
expect(fixture.debugElement.query(By.css('#bpm-tenant')).nativeElement.textContent).toContain('fake-tenant-name');
});
it('should show the ecm user information', async () => {
openUserInfo();
const ecmTab = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'))[0];
ecmTab.triggerEventHandler('click', null);
fixture.detectChanges();
await fixture.whenStable();
expect(fixture.debugElement.query(By.css('#ecm-full-name')).nativeElement.textContent).toContain('fake-ecm-first-name fake-ecm-last-name');
expect(fixture.debugElement.query(By.css('#ecm-job-title')).nativeElement.textContent).toContain('job-ecm-test');
});
});
});
});

View File

@@ -1,200 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Meta,
moduleMetadata,
Story
} from '@storybook/angular';
import { CoreStoryModule } from '../../testing/core.story.module';
import { UserInfoComponent } from './user-info.component';
import { UserInfoModule } from '../userinfo.module';
import { PeopleContentService } from './../../services/people-content.service';
import { BpmUserService } from './../../services/bpm-user.service';
import { IdentityUserService } from '../../auth/services/identity-user.service';
import { AuthenticationService } from '../../auth/services/authentication.service';
import { AuthenticationServiceMock } from './mocks/authentication.service.mock';
import {
BpmUserServiceMock,
IdentityUserServiceMock,
PeopleContentServiceMock
} from './mocks/user.service.mock';
export default {
component: UserInfoComponent,
title: 'Core/User Info/User Info',
decorators: [
moduleMetadata({
imports: [CoreStoryModule, UserInfoModule],
providers: [
{
provide: PeopleContentService,
useClass: PeopleContentServiceMock
},
{
provide: BpmUserService,
useClass: BpmUserServiceMock
},
{
provide: IdentityUserService,
useClass: IdentityUserServiceMock
},
{
provide: AuthenticationService,
useClass: AuthenticationServiceMock
}
]
})
],
argTypes: {
menuPositionX: {
description:
'Material Angular menu horizontal position in regard to User Info',
control: 'radio',
options: ['before', 'after'],
defaultValue: 'after',
table: {
type: { summary: 'MenuPositionX' },
defaultValue: { summary: 'after' }
}
},
menuPositionY: {
description:
'Material Angular menu vertical position in regard to User Info',
control: 'radio',
options: ['above', 'below'],
defaultValue: 'below',
table: {
type: { summary: 'MenuPositionY' },
defaultValue: { summary: 'below' }
}
},
showName: {
description:
'Determines if name should be shown next to user avatar',
control: 'boolean',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: true }
}
},
namePosition: {
description: 'User name position in regard to avatar',
control: 'radio',
options: ['left', 'right'],
defaultValue: 'right',
table: {
type: { summary: 'string' },
defaultValue: { summary: 'right' }
}
},
ecmBackgroundImage: {
description: 'Menu background banner image for ACS users',
control: {
disable: true
},
table: {
type: { summary: 'string' },
defaultValue: { summary: './assets/images/ecm-background.png' }
}
},
bpmBackgroundImage: {
description: 'Menu background banner image for APS users',
control: {
disable: true
},
table: {
type: {
summary: 'string'
},
defaultValue: {
summary: './assets/images/bpm-background.png'
}
}
}
}
} as Meta;
const template: Story<UserInfoComponent> = (args: UserInfoComponent) => ({
props: args
});
export const loginWithSSO = template.bind({});
loginWithSSO.decorators = [
moduleMetadata({
providers: [
{
provide: 'MODE',
useValue: 'default'
}
]
})
];
loginWithSSO.parameters = { layout: 'centered' };
export const loginWithSSOAndACS = template.bind({});
loginWithSSOAndACS.decorators = [
moduleMetadata({
providers: [
{
provide: 'MODE',
useValue: 'defaultEcm'
}
]
})
];
loginWithSSOAndACS.parameters = { layout: 'centered' };
export const loginWithAPSAndACS = template.bind({});
loginWithAPSAndACS.decorators = [
moduleMetadata({
providers: [
{
provide: 'MODE',
useValue: 'all'
}
]
})
];
loginWithAPSAndACS.parameters = { layout: 'centered' };
export const loginWithACS = template.bind({});
loginWithACS.decorators = [
moduleMetadata({
providers: [
{
provide: 'MODE',
useValue: 'ecm'
}
]
})
];
loginWithACS.parameters = { layout: 'centered' };
export const loginWithAPS = template.bind({});
loginWithAPS.decorators = [
moduleMetadata({
providers: [
{
provide: 'MODE',
useValue: 'bpm'
}
]
})
];
loginWithAPS.parameters = { layout: 'centered' };

View File

@@ -17,12 +17,12 @@
export * from './lib/about/index';
export * from './lib/viewer/index';
export * from './lib/userinfo/index';
export * from './lib/toolbar/index';
export * from './lib/pagination/index';
export * from './lib/login/index';
export * from './lib/language-menu/index';
export * from './lib/info-drawer/index';
export * from './lib/identity-user-info/index';
export * from './lib/datatable/data-column/index';
export * from './lib/datatable/index';
export * from './lib/context-menu/index';

View File

@@ -0,0 +1,18 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './public-api';

View File

@@ -0,0 +1,109 @@
<div
id="userinfo_container"
[class.adf-userinfo-name-right]="showOnRight"
(keyup)="onKeyPress($event)"
class="adf-userinfo-container"
*ngIf="canShow"
>
<span *ngIf="showName" id="adf-userinfo-bpm-name-display" class="adf-userinfo-name">
{{bpmUser | fullName}}
</span>
<button mat-button [matMenuTriggerFor]="menu" class="adf-userinfo-menu_button"
data-automation-id="adf-user-profile">
<div class="adf-userinfo-button-profile" id="user-profile">
<div *ngIf="ecmUser; else showBpmUserImage" id="ecm-user-image">
<div *ngIf="ecmUser.avatarId; else initialTemplate" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getEcmAvatar(ecmUser.avatarId)" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
<ng-template #initialTemplate>
<div [outerHTML]="ecmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
<ng-template #showBpmUserImage>
<div *ngIf="bpmUser" id="bpm-user-image">
<div *ngIf="bpmUser.pictureId; else initialTemplate" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getBpmUserImage()" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
<ng-template #initialTemplate>
<div [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
</ng-template>
</div>
</button>
<mat-menu #menu="matMenu" id="user-profile-lists" [xPosition]="menuPositionX" [yPosition]="menuPositionY"
[overlapTrigger]="false" class="adf-userinfo-menu">
<mat-tab-group id="tab-group-env" (click)="stopClosing($event)" selectedIndex="0" role="menuitem"
class="adf-userinfo-tab" [class.adf-hide-tab]="!ecmUser">
<mat-tab label="{{ 'USER_PROFILE.TAB.CS' | translate }}" role="dialog"
*ngIf="mode===userInfoMode.ALL">
<mat-card class="adf-userinfo-card" *ngIf="ecmUser">
<mat-card-header class="adf-userinfo-card-header"
[style.background-image]="'url(' + ecmBackgroundImage + ')'">
<div *ngIf="ecmUser.avatarId; else initialTemplate"
class="adf-userinfo-profile-container adf-hide-small">
<img class="adf-userinfo-profile-picture" id="ecm-user-detail-image"
alt="ecm-profile-image" [src]="getEcmAvatar(ecmUser.avatarId)"/>
</div>
<ng-template #initialTemplate>
<div
[outerHTML]="ecmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="ecm-username">{{ecmUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="ecm-full-name"
class="adf-userinfo__detail-title">{{ecmUser | fullName}}</span>
<span class="adf-userinfo__detail-profile" id="ecm-email"> {{ecmUser.email}} </span>
</div>
<div class="adf-userinfo-detail">
<span class="adf-userinfo__secondary-info" id="ecm-job-title-label">
{{ 'USER_PROFILE.LABELS.ECM.JOB_TITLE' | translate }}
<span id="ecm-job-title"
class="adf-userinfo__detail-profile"> {{ ecmUser.jobTitle ? ecmUser.jobTitle : 'N/A' }} </span>
</span>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
<mat-tab id="bpm-panel" label="{{ 'USER_PROFILE.TAB.PS' | translate }}" role="dialog"
*ngIf="mode===userInfoMode.PROCESS || mode===userInfoMode.ALL">
<mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header"
[style.background-image]="'url(' + bpmBackgroundImage + ')'">
<img *ngIf="bpmUser.pictureId; else initialTemplate"
class="adf-userinfo-profile-picture adf-hide-small" id="bpm-user-detail-image"
alt="bpm-profile-image" [src]="getBpmUserImage()"/>
<ng-template #initialTemplate>
<div
[outerHTML]="bpmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="bpm-username">{{bpmUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="bpm-full-name"
class="adf-userinfo__detail-title">{{ bpmUser | fullName }}</span>
<span class="adf-userinfo__detail-profile" id="bpm-email"> {{bpmUser.email}} </span>
</div>
<div class="adf-userinfo-detail">
<span id="bpm-tenant" class="adf-userinfo__secondary-info">
{{ 'USER_PROFILE.LABELS.BPM.TENANT' | translate }}
<span
class="adf-userinfo__detail-profile">{{ bpmUser.tenantName ? bpmUser.tenantName : '' }}</span>
</span>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
</mat-tab-group>
</mat-menu>
</div>

View File

@@ -0,0 +1,188 @@
.adf {
&-userinfo-container {
display: flex;
align-items: center;
padding: 0 5px;
}
&-userinfo-name-right {
flex-direction: row-reverse;
}
&-userinfo-name {
padding: 0 5px;
@media screen and (max-width: 959px) {
display: none;
}
}
&-userinfo-pic {
background: var(--adf-user-info-color);
display: inline-block;
width: 40px;
height: 40px;
border-radius: 100px;
text-align: center;
font-weight: bolder;
font-size: var(--theme-adf-picture-1-font-size);
text-transform: uppercase;
vertical-align: middle;
line-height: 40px;
}
&-userinfo-profile-image {
background: var(--adf-user-info-color);
text-align: center;
border-radius: 90%;
width: 40px;
height: 40px;
margin-right: 0;
cursor: pointer;
vertical-align: middle;
margin-left: 0;
}
&-userinfo-profile-container {
display: inline-block;
}
&-userinfo-menu_button.mat-button {
margin-right: 0;
border-radius: 90%;
padding: 0;
min-width: 40px;
height: 40px;
}
&-userinfo-tab .mat-tab-header {
align-self: center;
width: 100%;
min-width: 250px;
}
&-userinfo-tab .mat-tab-label {
flex: auto;
font-weight: 500;
font-size: var(--theme-body-1-font-size);
text-transform: uppercase;
line-height: 48px;
text-align: center;
}
&-userinfo-card-header {
align-items: center;
display: flex;
justify-content: stretch;
line-height: normal;
height: 100px;
box-sizing: border-box;
}
&-userinfo-card.mat-card {
padding: 0;
}
&-userinfo-supporting-text {
font-size: var(--theme-body-1-font-size);
font-weight: 400;
letter-spacing: 0;
line-height: 18px;
overflow: hidden;
padding: 32px;
column-count: 2;
display: flex;
justify-content: space-between;
@media screen and (max-width: 599px) {
padding: 10px;
}
}
&-userinfo-title {
font-size: var(--theme-title-font-size);
}
&-userinfo__detail-profile {
align-items: flex-start;
font-size: var(--theme-body-1-font-size);
font-weight: 400;
letter-spacing: 0;
line-height: 18px;
display: block;
padding: 0;
margin: 0;
}
&-userinfo__detail-title {
text-overflow: ellipsis;
font-size: var(--theme-subheading-2-font-size);
font-weight: 400;
letter-spacing: 0.04em;
line-height: 20px;
align-items: flex-start;
}
&-userinfo__secondary-info {
font-size: var(--theme-body-1-font-size);
font-weight: 400;
letter-spacing: 0;
line-height: 18px;
align-items: flex-end;
}
&-userinfo-profile-picture {
background: var(--adf-user-info-color);
background-size: cover;
border-radius: 50%;
height: 80px;
width: 80px;
margin-left: 0;
margin-right: 8px;
}
&-userinfo-profile-initials {
text-transform: uppercase;
background-size: cover;
background-color: var(--adf-user-info-color);
border-radius: 50%;
height: 80px;
width: 80px;
margin-left: 0;
margin-right: 8px;
font-size: 35px;
font-weight: 400;
letter-spacing: 0;
line-height: 78px;
overflow: hidden;
display: flex;
justify-content: space-around;
}
&-userinfo-button-profile {
display: inline-block;
border: 0;
vertical-align: middle;
}
&-userinfo-detail {
text-align: left;
}
&-hide-tab .mat-tab-label-active {
display: none !important;
}
}
@media only screen and (min-device-width: 480px) {
.mat-menu-panel.adf-userinfo-menu {
max-height: 450px;
min-width: 450px;
overflow: auto;
padding: 0;
}
}
.mat-menu-panel.adf-userinfo-menu .mat-menu-content {
padding: 0;
}

View File

@@ -0,0 +1,228 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CoreTestingModule,
fakeBpmUser,
BpmUserModel,
setupTestBed,
fakeEcmUser,
fakeEcmUserNoImage,
UserInfoMode
} from '@alfresco/adf-core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatMenuModule } from '@angular/material/menu';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { ProcessUserInfoComponent } from './process-user-info.component';
describe('ProcessUserInfoComponent', () => {
const profilePictureUrl = 'alfresco-logo.svg';
let component: ProcessUserInfoComponent;
let fixture: ComponentFixture<ProcessUserInfoComponent>;
let element: HTMLElement;
const openUserInfo = () => {
fixture.detectChanges();
const imageButton = element.querySelector<HTMLButtonElement>('#logged-user-img');
imageButton.click();
fixture.detectChanges();
};
const whenFixtureReady = async () => {
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule,
MatMenuModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(ProcessUserInfoComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
spyOn(window, 'requestAnimationFrame').and.returnValue(1);
});
afterEach(() => {
fixture.destroy();
});
it('should not show any image if the user is not logged in', () => {
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#logged-user-img')).toBeNull();
});
it('should NOT have users immediately after ngOnInit', () => {
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#ecm_username')).toBeNull();
expect(element.querySelector('#bpm_username')).toBeNull();
expect(element.querySelector('#user-profile-lists')).toBeNull();
});
describe('when user is logged on bpm', () => {
beforeEach(async () => {
component.bpmUser = fakeBpmUser;
component.isLoggedIn = true;
});
it('should show full name next the user image', async () => {
await whenFixtureReady();
openUserInfo();
const bpmUserName = fixture.debugElement.query(By.css('#bpm-username'));
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(bpmUserName).toBeDefined();
expect(bpmUserName).not.toBeNull();
expect(bpmUserName.nativeElement.innerHTML).toContain('fake-bpm-first-name fake-bpm-last-name');
});
it('should get the bpm current user image from the service', async () => {
spyOn(component, 'getBpmUserImage').and.returnValue(profilePictureUrl);
await whenFixtureReady();
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(element.querySelector('#logged-user-img')).not.toBeNull();
expect(element.querySelector('#logged-user-img').getAttribute('src')).toContain(profilePictureUrl);
});
it('should show last name if first name is null', async () => {
const wrongBpmUser: BpmUserModel = new BpmUserModel({
firstName: null,
lastName: 'fake-last-name'
});
component.bpmUser = wrongBpmUser;
await whenFixtureReady();
const fullNameElement = (element.querySelector('#adf-userinfo-bpm-name-display'));
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#adf-userinfo-bpm-name-display')).not.toBeNull();
expect(fullNameElement.textContent).toContain('fake-last-name');
expect(fullNameElement.textContent).not.toContain('fake-first-name');
});
it('should not show the tabs', async () => {
await whenFixtureReady();
openUserInfo();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#tab-group-env')).classes['adf-hide-tab']).toBeTruthy();
});
});
describe('when user is logged on bpm and ecm', () => {
beforeEach(async () => {
component.bpmUser = fakeBpmUser;
component.ecmUser = fakeEcmUser as any;
component.isLoggedIn = true;
component.mode = UserInfoMode.ALL;
});
it('should show the tabs', async () => {
await whenFixtureReady();
openUserInfo();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#tab-group-env')).classes['adf-hide-tab']).toBeFalsy();
});
it('should get the bpm user information', async () => {
spyOn(component, 'getBpmUserImage').and.returnValue(profilePictureUrl);
await whenFixtureReady();
openUserInfo();
const bpmTab = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'))[1];
bpmTab.triggerEventHandler('click', null);
fixture.detectChanges();
await fixture.whenStable();
const bpmUsername = fixture.debugElement.query(By.css('#bpm-username'));
const bpmImage = fixture.debugElement.query(By.css('#bpm-user-detail-image'));
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(bpmUsername).not.toBeNull();
expect(bpmImage).not.toBeNull();
expect(bpmImage.properties.src).toContain(profilePictureUrl);
expect(bpmUsername.nativeElement.textContent).toContain('fake-bpm-first-name fake-bpm-last-name');
expect(fixture.debugElement.query(By.css('#bpm-tenant')).nativeElement.textContent).toContain('fake-tenant-name');
});
it('should get the ecm user information', async () => {
spyOn(component, 'getEcmAvatar').and.returnValue(profilePictureUrl);
await whenFixtureReady();
openUserInfo();
const ecmUsername = fixture.debugElement.query(By.css('#ecm-username'));
const ecmImage = fixture.debugElement.query(By.css('#ecm-user-detail-image'));
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(ecmUsername).not.toBeNull();
expect(ecmImage).not.toBeNull();
expect(ecmImage.properties.src).toContain(profilePictureUrl);
expect(fixture.debugElement.query(By.css('#ecm-full-name')).nativeElement.textContent).toContain('fake-ecm-first-name fake-ecm-last-name');
expect(fixture.debugElement.query(By.css('#ecm-job-title')).nativeElement.textContent).toContain('job-ecm-test');
});
it('should show the ecm image if exists', async () => {
spyOn(component, 'getEcmAvatar').and.returnValue(profilePictureUrl);
await whenFixtureReady();
openUserInfo();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#logged-user-img')).toBeDefined();
expect(element.querySelector('#logged-user-img').getAttribute('src')).toEqual(profilePictureUrl);
});
it('should show the ecm initials if the ecm user has no image', async () => {
component.ecmUser = fakeEcmUserNoImage as any;
await whenFixtureReady();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('[data-automation-id="user-initials-image"]').textContent).toContain('ff');
});
it('should show the tabs for the env', async () => {
await whenFixtureReady();
openUserInfo();
const tabGroup = fixture.debugElement.query(By.css('#tab-group-env'));
const tabs = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'));
expect(tabGroup).not.toBeNull();
expect(tabGroup.classes['adf-hide-tab']).toBeFalsy();
expect(tabs.length).toBe(2);
});
it('should not close the menu when a tab is clicked', async () => {
await whenFixtureReady();
openUserInfo();
const tabGroup = fixture.debugElement.query(By.css('#tab-group-env'));
const tabs = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'));
expect(tabGroup).not.toBeNull();
tabs[1].triggerEventHandler('click', null);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#user-profile-lists'))).not.toBeNull();
});
});
});

View File

@@ -0,0 +1,112 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BpmUserModel, BpmUserService, EcmUserModel, PeopleContentService, UserInfoMode } from '@alfresco/adf-core';
import { Component, Input, OnDestroy, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { Subject } from 'rxjs';
@Component({
selector: 'adf-process-user-info',
templateUrl: './process-user-info.component.html',
styleUrls: ['./process-user-info.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ProcessUserInfoComponent implements OnDestroy {
@ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
@Input()
isLoggedIn: boolean;
@Input()
bpmUser: BpmUserModel;
@Input()
ecmUser: EcmUserModel;
@Input()
mode: UserInfoMode = UserInfoMode.PROCESS;
/** Custom path for the background banner image for APS users. */
@Input()
bpmBackgroundImage: string = './assets/images/bpm-background.png';
/** Custom path for the background banner image for ACS users. */
@Input()
ecmBackgroundImage: string = './assets/images/ecm-background.png';
/** Custom choice for opening the menu at the bottom. Can be `before` or `after`. */
@Input()
menuPositionX: MenuPositionX = 'after';
/** Custom choice for opening the menu at the bottom. Can be `above` or `below`. */
@Input()
menuPositionY: MenuPositionY = 'below';
/** Shows/hides the username next to the user info button. */
@Input()
showName: boolean = true;
/** When the username is shown, this defines its position relative to the user info button.
* Can be `right` or `left`.
*/
@Input()
namePosition: string = 'right';
userInfoMode = UserInfoMode;
private destroy$ = new Subject();
constructor(private bpmUserService: BpmUserService, private peopleContentService: PeopleContentService) {
}
ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
onKeyPress(event: KeyboardEvent) {
this.closeUserModal(event);
}
private closeUserModal($event: KeyboardEvent) {
if ($event.keyCode === 27) {
this.trigger.closeMenu();
}
}
stopClosing(event: Event) {
event.stopPropagation();
}
getBpmUserImage(): string {
return this.bpmUserService.getCurrentUserProfileImage();
}
getEcmAvatar(avatarId: string): string {
return this.peopleContentService.getUserProfileImage(avatarId);
}
get showOnRight(): boolean {
return this.namePosition === 'right';
}
get canShow(): boolean {
return this.isLoggedIn && !!this.bpmUser && !!this.mode;
}
}

View File

@@ -0,0 +1,41 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProcessUserInfoComponent } from './process-user-info.component';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatMenuModule } from '@angular/material/menu';
import { MatTabsModule } from '@angular/material/tabs';
import { TranslateModule } from '@ngx-translate/core';
import { PipeModule } from '@alfresco/adf-core';
@NgModule({
declarations: [ProcessUserInfoComponent],
imports: [
CommonModule,
MatButtonModule,
MatMenuModule,
MatTabsModule,
MatCardModule,
TranslateModule,
PipeModule
],
exports: [ProcessUserInfoComponent]
})
export class ProcessUserInfoModule {}

View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './process-user-info.component';
export * from './process-user-info.module';

View File

@@ -32,6 +32,7 @@ import { FormModule } from './form/form.module';
import { ProcessFormRenderingService } from './form/process-form-rendering.service';
import { ProcessServicesPipeModule } from './pipes/process-services-pipe.module';
import { TaskCommentsModule } from './task-comments/task-comments.module';
import { ProcessUserInfoModule } from './process-user-info/process-user-info.module';
@NgModule({
imports: [
@@ -45,6 +46,7 @@ import { TaskCommentsModule } from './task-comments/task-comments.module';
TaskListModule,
TaskCommentsModule,
AppsListModule,
ProcessUserInfoModule,
AttachmentModule,
PeopleModule,
FormModule,
@@ -69,6 +71,7 @@ import { TaskCommentsModule } from './task-comments/task-comments.module';
TaskListModule,
TaskCommentsModule,
AppsListModule,
ProcessUserInfoModule,
AttachmentModule,
PeopleModule,
FormModule,

View File

@@ -19,6 +19,7 @@ export * from './lib/process-list/index';
export * from './lib/task-list/index';
export * from './lib/app-list/index';
export * from './lib/attachment/index';
export * from './lib/process-user-info/index';
export * from './lib/process-comments/index';
export * from './lib/people/index';
export * from './lib/form/index';