From 96075ae4569765567fd4e8e5d1b0963c1d0e6e0c Mon Sep 17 00:00:00 2001 From: Diogo Bastos <50139916+DiogoABastos@users.noreply.github.com> Date: Wed, 15 Feb 2023 14:47:43 +0000 Subject: [PATCH] [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 --- demo-shell/src/app/app.module.ts | 2 + .../app-layout/app-layout.component.html | 3 +- .../user-info/user-info.component.html | 21 + .../user-info}/user-info.component.ts | 88 +-- .../components/content-user-info.component.md | 38 ++ ...ent.md => identity-user-info.component.md} | 16 +- .../components/process-user-info.component.md | 38 ++ docs/upgrade-guide/upgrade50-60.md | 11 + .../content-user-info.component.html | 111 ++++ .../content-user-info.component.scss} | 0 .../content-user-info.component.spec.ts | 272 ++++++++ .../content-user-info.component.ts | 108 ++++ .../content-user-info.module.ts | 41 ++ .../src/lib/content-user-info}/index.ts | 0 .../src/lib/content-user-info/public-api.ts | 20 + .../src/lib/content.module.ts | 3 + lib/content-services/src/public-api.ts | 1 + lib/core/src/lib/common/index.ts | 1 + .../lib/common/models/user-info-mode.enum.ts | 24 + lib/core/src/lib/core.module.ts | 6 +- .../identity-user-info.component.html | 45 ++ .../identity-user-info.component.scss | 188 ++++++ .../identity-user-info.component.spec.ts | 139 ++++ .../identity-user-info.component.stories.ts | 128 ++++ .../identity-user-info.component.ts | 89 +++ .../identity-user-info.module.ts} | 21 +- .../index.ts} | 3 +- .../src/lib/identity-user-info/public-api.ts | 20 + .../mocks/authentication.service.mock.ts | 76 --- .../components/mocks/user.service.mock.ts | 78 --- .../components/user-info.component.html | 162 ----- .../components/user-info.component.spec.ts | 592 ------------------ .../components/user-info.component.stories.ts | 200 ------ lib/core/src/public-api.ts | 2 +- .../src/lib/process-user-info/index.ts | 18 + .../process-user-info.component.html | 109 ++++ .../process-user-info.component.scss | 188 ++++++ .../process-user-info.component.spec.ts | 228 +++++++ .../process-user-info.component.ts | 112 ++++ .../process-user-info.module.ts | 41 ++ .../src/lib/process-user-info/public-api.ts | 20 + .../src/lib/process.module.ts | 3 + lib/process-services/src/public-api.ts | 1 + 43 files changed, 2057 insertions(+), 1210 deletions(-) create mode 100644 demo-shell/src/app/components/app-layout/user-info/user-info.component.html rename {lib/core/src/lib/userinfo/components => demo-shell/src/app/components/app-layout/user-info}/user-info.component.ts (54%) create mode 100644 docs/content-services/components/content-user-info.component.md rename docs/core/components/{user-info.component.md => identity-user-info.component.md} (69%) create mode 100644 docs/process-services/components/process-user-info.component.md create mode 100644 lib/content-services/src/lib/content-user-info/content-user-info.component.html rename lib/{core/src/lib/userinfo/components/user-info.component.scss => content-services/src/lib/content-user-info/content-user-info.component.scss} (100%) create mode 100644 lib/content-services/src/lib/content-user-info/content-user-info.component.spec.ts create mode 100644 lib/content-services/src/lib/content-user-info/content-user-info.component.ts create mode 100644 lib/content-services/src/lib/content-user-info/content-user-info.module.ts rename lib/{core/src/lib/userinfo => content-services/src/lib/content-user-info}/index.ts (100%) create mode 100644 lib/content-services/src/lib/content-user-info/public-api.ts create mode 100644 lib/core/src/lib/common/models/user-info-mode.enum.ts create mode 100644 lib/core/src/lib/identity-user-info/identity-user-info.component.html create mode 100644 lib/core/src/lib/identity-user-info/identity-user-info.component.scss create mode 100644 lib/core/src/lib/identity-user-info/identity-user-info.component.spec.ts create mode 100644 lib/core/src/lib/identity-user-info/identity-user-info.component.stories.ts create mode 100644 lib/core/src/lib/identity-user-info/identity-user-info.component.ts rename lib/core/src/lib/{userinfo/userinfo.module.ts => identity-user-info/identity-user-info.module.ts} (85%) rename lib/core/src/lib/{userinfo/public-api.ts => identity-user-info/index.ts} (87%) create mode 100644 lib/core/src/lib/identity-user-info/public-api.ts delete mode 100644 lib/core/src/lib/userinfo/components/mocks/authentication.service.mock.ts delete mode 100644 lib/core/src/lib/userinfo/components/mocks/user.service.mock.ts delete mode 100644 lib/core/src/lib/userinfo/components/user-info.component.html delete mode 100644 lib/core/src/lib/userinfo/components/user-info.component.spec.ts delete mode 100644 lib/core/src/lib/userinfo/components/user-info.component.stories.ts create mode 100644 lib/process-services/src/lib/process-user-info/index.ts create mode 100644 lib/process-services/src/lib/process-user-info/process-user-info.component.html create mode 100644 lib/process-services/src/lib/process-user-info/process-user-info.component.scss create mode 100644 lib/process-services/src/lib/process-user-info/process-user-info.component.spec.ts create mode 100644 lib/process-services/src/lib/process-user-info/process-user-info.component.ts create mode 100644 lib/process-services/src/lib/process-user-info/process-user-info.module.ts create mode 100644 lib/process-services/src/lib/process-user-info/public-api.ts diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts index c736763b8c..655e7f26f1 100644 --- a/demo-shell/src/app/app.module.ts +++ b/demo-shell/src/app/app.module.ts @@ -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, diff --git a/demo-shell/src/app/components/app-layout/app-layout.component.html b/demo-shell/src/app/components/app-layout/app-layout.component.html index 2fa808e4be..259ff7f3a2 100644 --- a/demo-shell/src/app/components/app-layout/app-layout.component.html +++ b/demo-shell/src/app/components/app-layout/app-layout.component.html @@ -22,8 +22,7 @@
- - + + + + + + + +
+ ecm-profile-image +
+ +
+
+ +
{{ecmUser | fullName}}
+
+ +
+
+ {{ecmUser | fullName}} + {{ecmUser.email}} + + {{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }} +
+
+ + {{ 'USER_PROFILE.LABELS.ECM.JOB_TITLE' | translate }} + {{ ecmUser.jobTitle ? ecmUser.jobTitle : 'N/A' }} + +
+
+
+
+
+ + + +
+ ecm-profile-image +
+ +
+
+
{{identityUser | fullName}}
+
+ +
+
+ {{identityUser | fullName}} + {{identityUser.email}} + + {{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }} +
+
+
+
+
+
+
+ diff --git a/lib/core/src/lib/userinfo/components/user-info.component.scss b/lib/content-services/src/lib/content-user-info/content-user-info.component.scss similarity index 100% rename from lib/core/src/lib/userinfo/components/user-info.component.scss rename to lib/content-services/src/lib/content-user-info/content-user-info.component.scss diff --git a/lib/content-services/src/lib/content-user-info/content-user-info.component.spec.ts b/lib/content-services/src/lib/content-user-info/content-user-info.component.spec.ts new file mode 100644 index 0000000000..39a3282e63 --- /dev/null +++ b/lib/content-services/src/lib/content-user-info/content-user-info.component.spec.ts @@ -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; + 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('#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('[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('[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('
WA
'); + 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(); + }); + }); + }); +}); diff --git a/lib/content-services/src/lib/content-user-info/content-user-info.component.ts b/lib/content-services/src/lib/content-user-info/content-user-info.component.ts new file mode 100644 index 0000000000..e738a65c78 --- /dev/null +++ b/lib/content-services/src/lib/content-user-info/content-user-info.component.ts @@ -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; + } +} diff --git a/lib/content-services/src/lib/content-user-info/content-user-info.module.ts b/lib/content-services/src/lib/content-user-info/content-user-info.module.ts new file mode 100644 index 0000000000..0cc0e14bec --- /dev/null +++ b/lib/content-services/src/lib/content-user-info/content-user-info.module.ts @@ -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 {} diff --git a/lib/core/src/lib/userinfo/index.ts b/lib/content-services/src/lib/content-user-info/index.ts similarity index 100% rename from lib/core/src/lib/userinfo/index.ts rename to lib/content-services/src/lib/content-user-info/index.ts diff --git a/lib/content-services/src/lib/content-user-info/public-api.ts b/lib/content-services/src/lib/content-user-info/public-api.ts new file mode 100644 index 0000000000..34c490c67e --- /dev/null +++ b/lib/content-services/src/lib/content-user-info/public-api.ts @@ -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'; diff --git a/lib/content-services/src/lib/content.module.ts b/lib/content-services/src/lib/content.module.ts index ec077a74e6..d288f143db 100644 --- a/lib/content-services/src/lib/content.module.ts +++ b/lib/content-services/src/lib/content.module.ts @@ -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, diff --git a/lib/content-services/src/public-api.ts b/lib/content-services/src/public-api.ts index 99864b38bc..f08cf2f20e 100644 --- a/lib/content-services/src/public-api.ts +++ b/lib/content-services/src/public-api.ts @@ -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'; diff --git a/lib/core/src/lib/common/index.ts b/lib/core/src/lib/common/index.ts index cfc9af21af..7475094691 100644 --- a/lib/core/src/lib/common/index.ts +++ b/lib/core/src/lib/common/index.ts @@ -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'; diff --git a/lib/core/src/lib/common/models/user-info-mode.enum.ts b/lib/core/src/lib/common/models/user-info-mode.enum.ts new file mode 100644 index 0000000000..53527d41f4 --- /dev/null +++ b/lib/core/src/lib/common/models/user-info-mode.enum.ts @@ -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' +} diff --git a/lib/core/src/lib/core.module.ts b/lib/core/src/lib/core.module.ts index 848f0e2d31..9c3a5af760 100644 --- a/lib/core/src/lib/core.module.ts +++ b/lib/core/src/lib/core.module.ts @@ -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, diff --git a/lib/core/src/lib/identity-user-info/identity-user-info.component.html b/lib/core/src/lib/identity-user-info/identity-user-info.component.html new file mode 100644 index 0000000000..a5a7189003 --- /dev/null +++ b/lib/core/src/lib/identity-user-info/identity-user-info.component.html @@ -0,0 +1,45 @@ +
+ + {{identityUser | fullName}} + + + + + + + +
{{identityUser | fullName}}
+
+ +
+
+ {{identityUser | fullName}} + {{identityUser.email}} + + {{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }} +
+
+
+
+
+
+
+
diff --git a/lib/core/src/lib/identity-user-info/identity-user-info.component.scss b/lib/core/src/lib/identity-user-info/identity-user-info.component.scss new file mode 100644 index 0000000000..4161e22a04 --- /dev/null +++ b/lib/core/src/lib/identity-user-info/identity-user-info.component.scss @@ -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; +} diff --git a/lib/core/src/lib/identity-user-info/identity-user-info.component.spec.ts b/lib/core/src/lib/identity-user-info/identity-user-info.component.spec.ts new file mode 100644 index 0000000000..6022ba9ca9 --- /dev/null +++ b/lib/core/src/lib/identity-user-info/identity-user-info.component.spec.ts @@ -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; + 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('#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'); + }); + }); +}); diff --git a/lib/core/src/lib/identity-user-info/identity-user-info.component.stories.ts b/lib/core/src/lib/identity-user-info/identity-user-info.component.stories.ts new file mode 100644 index 0000000000..dde3a8e863 --- /dev/null +++ b/lib/core/src/lib/identity-user-info/identity-user-info.component.stories.ts @@ -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 = (args: IdentityUserInfoComponent) => ({ + props: args +}); + +export const loginWithSSO = template.bind({}); +loginWithSSO.args = { + identityUser: fakeIdentityUser +}; +loginWithSSO.parameters = { layout: 'centered' }; diff --git a/lib/core/src/lib/identity-user-info/identity-user-info.component.ts b/lib/core/src/lib/identity-user-info/identity-user-info.component.ts new file mode 100644 index 0000000000..ced010ff00 --- /dev/null +++ b/lib/core/src/lib/identity-user-info/identity-user-info.component.ts @@ -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; + } +} diff --git a/lib/core/src/lib/userinfo/userinfo.module.ts b/lib/core/src/lib/identity-user-info/identity-user-info.module.ts similarity index 85% rename from lib/core/src/lib/userinfo/userinfo.module.ts rename to lib/core/src/lib/identity-user-info/identity-user-info.module.ts index 356cf7ce7b..38e688f006 100644 --- a/lib/core/src/lib/userinfo/userinfo.module.ts +++ b/lib/core/src/lib/identity-user-info/identity-user-info.module.ts @@ -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 {} diff --git a/lib/core/src/lib/userinfo/public-api.ts b/lib/core/src/lib/identity-user-info/index.ts similarity index 87% rename from lib/core/src/lib/userinfo/public-api.ts rename to lib/core/src/lib/identity-user-info/index.ts index 7685fe3d4e..a7e30cc675 100644 --- a/lib/core/src/lib/userinfo/public-api.ts +++ b/lib/core/src/lib/identity-user-info/index.ts @@ -15,5 +15,4 @@ * limitations under the License. */ -export * from './components/user-info.component'; -export * from './userinfo.module'; +export * from './public-api'; diff --git a/lib/core/src/lib/identity-user-info/public-api.ts b/lib/core/src/lib/identity-user-info/public-api.ts new file mode 100644 index 0000000000..f7dabec92a --- /dev/null +++ b/lib/core/src/lib/identity-user-info/public-api.ts @@ -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'; diff --git a/lib/core/src/lib/userinfo/components/mocks/authentication.service.mock.ts b/lib/core/src/lib/userinfo/components/mocks/authentication.service.mock.ts deleted file mode 100644 index 999236c77d..0000000000 --- a/lib/core/src/lib/userinfo/components/mocks/authentication.service.mock.ts +++ /dev/null @@ -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 = new ReplaySubject(1); - - onLogout: ReplaySubject = new ReplaySubject(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'; - } -} diff --git a/lib/core/src/lib/userinfo/components/mocks/user.service.mock.ts b/lib/core/src/lib/userinfo/components/mocks/user.service.mock.ts deleted file mode 100644 index 25bbe8ad1e..0000000000 --- a/lib/core/src/lib/userinfo/components/mocks/user.service.mock.ts +++ /dev/null @@ -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; -} diff --git a/lib/core/src/lib/userinfo/components/user-info.component.html b/lib/core/src/lib/userinfo/components/user-info.component.html deleted file mode 100644 index 3b4321c412..0000000000 --- a/lib/core/src/lib/userinfo/components/user-info.component.html +++ /dev/null @@ -1,162 +0,0 @@ -
- - - {{identityUser | fullName}} - - {{ecmUser | fullName}} - - {{bpmUser | fullName}} - - - - - - - - - - -
- ecm-profile-image -
- -
-
- -
{{ecmUser | fullName}}
-
- -
-
- {{ecmUser | fullName}} - {{ecmUser.email}} - - {{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }} -
-
- - {{ 'USER_PROFILE.LABELS.ECM.JOB_TITLE' | translate }} - {{ ecmUser.jobTitle ? ecmUser.jobTitle : 'N/A' }} - -
-
-
-
-
- - - - bpm-profile-image - -
-
-
{{bpmUser | fullName}}
-
- -
-
- {{ bpmUser | fullName }} - {{bpmUser.email}} -
-
- - {{ 'USER_PROFILE.LABELS.BPM.TENANT' | translate }} - {{ bpmUser.tenantName ? bpmUser.tenantName : '' }} - -
-
-
-
-
- - - -
-
- ecm-profile-image -
-
- -
-
-
{{identityUser | fullName}}
-
- -
-
- {{identityUser | fullName}} - {{identityUser.email}} - - {{ 'USER_PROFILE.LABELS.MY_PROFILE' | translate }} -
-
-
-
-
-
-
-
diff --git a/lib/core/src/lib/userinfo/components/user-info.component.spec.ts b/lib/core/src/lib/userinfo/components/user-info.component.spec.ts deleted file mode 100644 index fed9436671..0000000000 --- a/lib/core/src/lib/userinfo/components/user-info.component.spec.ts +++ /dev/null @@ -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; - 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('#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('[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('[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('
WA
'); - 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('#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'); - }); - }); - }); -}); diff --git a/lib/core/src/lib/userinfo/components/user-info.component.stories.ts b/lib/core/src/lib/userinfo/components/user-info.component.stories.ts deleted file mode 100644 index cce7dcd3b2..0000000000 --- a/lib/core/src/lib/userinfo/components/user-info.component.stories.ts +++ /dev/null @@ -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 = (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' }; diff --git a/lib/core/src/public-api.ts b/lib/core/src/public-api.ts index b94e455357..191cc5cb38 100644 --- a/lib/core/src/public-api.ts +++ b/lib/core/src/public-api.ts @@ -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'; diff --git a/lib/process-services/src/lib/process-user-info/index.ts b/lib/process-services/src/lib/process-user-info/index.ts new file mode 100644 index 0000000000..a7e30cc675 --- /dev/null +++ b/lib/process-services/src/lib/process-user-info/index.ts @@ -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'; diff --git a/lib/process-services/src/lib/process-user-info/process-user-info.component.html b/lib/process-services/src/lib/process-user-info/process-user-info.component.html new file mode 100644 index 0000000000..bc5582dcdf --- /dev/null +++ b/lib/process-services/src/lib/process-user-info/process-user-info.component.html @@ -0,0 +1,109 @@ +
+ + {{bpmUser | fullName}} + + + + + + + +
+ ecm-profile-image +
+ +
+
+ +
{{ecmUser | fullName}}
+
+ +
+
+ {{ecmUser | fullName}} + {{ecmUser.email}} +
+
+ + {{ 'USER_PROFILE.LABELS.ECM.JOB_TITLE' | translate }} + {{ ecmUser.jobTitle ? ecmUser.jobTitle : 'N/A' }} + +
+
+
+
+
+ + + + bpm-profile-image + +
+
+
{{bpmUser | fullName}}
+
+ +
+
+ {{ bpmUser | fullName }} + {{bpmUser.email}} +
+
+ + {{ 'USER_PROFILE.LABELS.BPM.TENANT' | translate }} + {{ bpmUser.tenantName ? bpmUser.tenantName : '' }} + +
+
+
+
+
+
+
+
diff --git a/lib/process-services/src/lib/process-user-info/process-user-info.component.scss b/lib/process-services/src/lib/process-user-info/process-user-info.component.scss new file mode 100644 index 0000000000..4161e22a04 --- /dev/null +++ b/lib/process-services/src/lib/process-user-info/process-user-info.component.scss @@ -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; +} diff --git a/lib/process-services/src/lib/process-user-info/process-user-info.component.spec.ts b/lib/process-services/src/lib/process-user-info/process-user-info.component.spec.ts new file mode 100644 index 0000000000..0af79be5c7 --- /dev/null +++ b/lib/process-services/src/lib/process-user-info/process-user-info.component.spec.ts @@ -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; + let element: HTMLElement; + + const openUserInfo = () => { + fixture.detectChanges(); + const imageButton = element.querySelector('#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(); + }); + }); +}); diff --git a/lib/process-services/src/lib/process-user-info/process-user-info.component.ts b/lib/process-services/src/lib/process-user-info/process-user-info.component.ts new file mode 100644 index 0000000000..bb076f310e --- /dev/null +++ b/lib/process-services/src/lib/process-user-info/process-user-info.component.ts @@ -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; + } +} diff --git a/lib/process-services/src/lib/process-user-info/process-user-info.module.ts b/lib/process-services/src/lib/process-user-info/process-user-info.module.ts new file mode 100644 index 0000000000..b92de6e7d6 --- /dev/null +++ b/lib/process-services/src/lib/process-user-info/process-user-info.module.ts @@ -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 {} diff --git a/lib/process-services/src/lib/process-user-info/public-api.ts b/lib/process-services/src/lib/process-user-info/public-api.ts new file mode 100644 index 0000000000..0751688a2f --- /dev/null +++ b/lib/process-services/src/lib/process-user-info/public-api.ts @@ -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'; diff --git a/lib/process-services/src/lib/process.module.ts b/lib/process-services/src/lib/process.module.ts index 53a5a741f3..289b112049 100644 --- a/lib/process-services/src/lib/process.module.ts +++ b/lib/process-services/src/lib/process.module.ts @@ -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, diff --git a/lib/process-services/src/public-api.ts b/lib/process-services/src/public-api.ts index 4ddfe63f81..0ab696f107 100644 --- a/lib/process-services/src/public-api.ts +++ b/lib/process-services/src/public-api.ts @@ -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';