[ADF-3666] User Profile - Provide a way to show SSO Logged-In User (#3976)

* * Created IdentityUserService
* Created IdentityUserModel
* Added JWT lib into package.json

* * Provided a way to show SSO user info

* * Added  condition to check sso login

* * Refactored userInfo component* Updated unit tests to the recent changes

* * Created IdenityUser model
* Refactored userInfoComponent

* * Modified userInfoComponent
* Modified unit tests

* * Used Fullname pipe* Removed the logic that we had to display fullname in the ecm/bpm/identity model* Created JwtHelperService* Modified ecm/bpm services * added test cases to the identity service

* * Moved jwt service to core/service

* * Updated userInfo doc

* * Added missing return types

* * Created a mockToken* Added unit tests to the JwtHelperService* Updated identityUserService unit test

* Update jwt-helper.service.spec.ts

* * Updated bpm/ecm/identity services
This commit is contained in:
siva kumar
2018-11-22 15:33:19 +05:30
committed by Maurizio Vitale
parent a39e44e4a9
commit 974929e76d
19 changed files with 629 additions and 153 deletions

View File

@@ -32,3 +32,4 @@ Shows user information.
The component shows a round icon for the user and will show extra information about The component shows a round icon for the user and will show extra information about
the user when clicked. the user when clicked.
If user is logged in with both ACS and APS, the ACS image will be shown. If user is logged in with both ACS and APS, the ACS image will be shown.
In case of SSO authentication, the information related to the user like firstname, lastname will be fetched using the Keycloak Api

View File

@@ -24,7 +24,6 @@ export let fakeBpmUserNoImage = {
externalId: 'fake-external-id', externalId: 'fake-external-id',
firstName: 'fake-first-name', firstName: 'fake-first-name',
lastName: 'fake-last-name', lastName: 'fake-last-name',
fullname: 'fake-full-name',
groups: [], groups: [],
id: 'fake-id', id: 'fake-id',
lastUpdate: 'fake-update-date', lastUpdate: 'fake-update-date',
@@ -47,7 +46,6 @@ export let fakeBpmUser = {
externalId: 'fake-external-id', externalId: 'fake-external-id',
firstName: 'fake-bpm-first-name', firstName: 'fake-bpm-first-name',
lastName: 'fake-bpm-last-name', lastName: 'fake-bpm-last-name',
fullname: 'fake-bpm-full-name',
groups: [], groups: [],
id: 'fake-id', id: 'fake-id',
lastUpdate: 'fake-update-date', lastUpdate: 'fake-update-date',
@@ -70,7 +68,6 @@ export let fakeBpmEditedUser = {
externalId: 'fake-external-id', externalId: 'fake-external-id',
firstName: 'fake-first-name', firstName: 'fake-first-name',
lastName: 'fake-last-name', lastName: 'fake-last-name',
fullname: 'fake-full-name',
groups: [], groups: [],
id: 'fake-id', id: 'fake-id',
lastUpdate: 'fake-update-date', lastUpdate: 'fake-update-date',

View File

@@ -0,0 +1,22 @@
/*!
* @license
* Copyright 2016 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 let mockToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.'
+ 'eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJqb2huRG9lQGdtYWlsLmNvbSI'
+ 'sImdpdmVuX25hbWUiOiJKb2huIERvZSIsImp0aSI6IjY1ZGMzZTEyLWJhNGUtNDQ'
+ '2Mi1iZjAyLTBlZGQ2MTYwM2M2NCIsImlhdCI6MTU0MjcyMTYxMywiZXhwIjoxNTQyNzI3NzY5fQ'
+ '.cUxMzfiJeLwh9Er2CBn_y8ehQgSm_s2-NHehx-SRZKg';

View File

@@ -36,3 +36,4 @@ export * from './form/formDefinitionVisibility.mock';
export * from './form/start-form.component.mock'; export * from './form/start-form.component.mock';
export * from './form/form.service.mock'; export * from './form/form.service.mock';
export * from './form/widget-visibility.service.mock'; export * from './form/widget-visibility.service.mock';
export * from './jwt-helper.service.spec';

View File

@@ -0,0 +1,45 @@
/*!
* @license
* Copyright 2016 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 { TestBed } from '@angular/core/testing';
import { JwtHelperService } from './jwt-helper.service';
import { mockToken } from './../mock/jwt-helper.service.spec';
describe('JwtHelperService', () => {
let jwtHelperService: JwtHelperService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [JwtHelperService]
});
jwtHelperService = TestBed.get(JwtHelperService);
});
it('should be able to create the service', () => {
expect(jwtHelperService).not.toBeNull();
expect(jwtHelperService).toBeDefined();
});
it('Should decode the Jwt token', () => {
const result = jwtHelperService.decodeToken(mockToken);
expect(result).toBeDefined();
expect(result).not.toBeNull('');
expect(result['given_name']).toBe('John Doe');
expect(result['email']).toBe('johnDoe@gmail.com');
});
});

View File

@@ -0,0 +1,62 @@
/*!
* @license
* Copyright 2016 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 { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class JwtHelperService {
constructor() {}
decodeToken(token): Object {
let parts = token.split('.');
if (parts.length !== 3) {
throw new Error('JWT must have 3 parts');
}
let decoded = this.urlBase64Decode(parts[1]);
if (!decoded) {
throw new Error('Cannot decode the token');
}
return JSON.parse(decoded);
}
private urlBase64Decode(token): string {
let output = token.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0: {
break;
}
case 2: {
output += '==';
break;
}
case 3: {
output += '=';
break;
}
default: {
throw new Error('Illegal base64url string!');
}
}
return decodeURIComponent(escape(window.atob(output)));
}
}

View File

@@ -50,3 +50,4 @@ export * from './search-configuration.service';
export * from './comment-content.service'; export * from './comment-content.service';
export * from './login-dialog.service'; export * from './login-dialog.service';
export * from './external-alfresco-api.service'; export * from './external-alfresco-api.service';
export * from './jwt-helper.service';

View File

@@ -1,48 +1,68 @@
<div id="userinfo_container" <div id="userinfo_container" [class.adf-userinfo-name-right]="showOnRight()"
[class.adf-userinfo-name-right]="showOnRight()"
class="adf-userinfo-container" *ngIf="isLoggedIn()"> class="adf-userinfo-container" *ngIf="isLoggedIn()">
<span *ngIf="ecmUser && showName" id="adf-userinfo-ecm-name-display"
class="adf-userinfo-name">{{ecmUser.fullNameDisplay}}</span> <ng-container *ngIf="showName">
<span *ngIf="bpmUser && !ecmUser && showName" id="adf-userinfo-bpm-name-display" <span *ngIf="identityUser$ | async as identityUser; else showBpmAndEcmUserFullNames" id="adf-userinfo-identity-name-display"
class="adf-userinfo-name">{{bpmUser.fullNameDisplay}}</span> class="adf-userinfo-name">{{identityUser | fullName}}</span>
<ng-template #showBpmAndEcmUserFullNames>
<span *ngIf="ecmUser$ | async as ecmUser; else showBpmUserFullName" id="adf-userinfo-ecm-name-display"
class="adf-userinfo-name">{{ecmUser | fullName}}</span>
<ng-template #showBpmUserFullName>
<span *ngIf="bpmUser$ | async as bpmUser" id="adf-userinfo-bpm-name-display"
class="adf-userinfo-name">{{bpmUser | fullName}}</span>
</ng-template>
</ng-template>
</ng-container>
<button mat-button [matMenuTriggerFor]="menu" class="adf-userinfo-menu_button" data-automation-id="adf-user-profile"> <button mat-button [matMenuTriggerFor]="menu" class="adf-userinfo-menu_button" data-automation-id="adf-user-profile">
<div class="adf-userinfo-button-profile" id="user-profile"> <div class="adf-userinfo-button-profile" id="user-profile">
<div *ngIf="bpmUser && !ecmUser" id="bpm-user-image"> <div *ngIf="identityUser$ | async as identityUser; else showBpmAndEcmUserImage" id="identity-user-image">
<div *ngIf="!hasBpmUserPictureId()" [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-pic'"></div> <div [outerHTML]="identityUser | usernameInitials:'adf-userinfo-pic'"></div>
<div *ngIf="hasBpmUserPictureId()" class="adf-userinfo-profile-container"> </div>
<img id="logged-user-img" [src]="bpmUserImage" alt="user-info-profile-button" <ng-template #showBpmAndEcmUserImage>
<div *ngIf="ecmUser$ | async as ecmUser; else showBpmUserImage" id="ecm-user-image">
<div *ngIf="ecmUser.avatarId; else initialTemplate" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getEcmAvatar(ecmUser.avatarId)" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/> class="adf-userinfo-profile-image"/>
</div> </div>
<ng-template #initialTemplate>
<div [outerHTML]="ecmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div> </div>
<div *ngIf="ecmUser" id="ecm-user-image"> <ng-template #showBpmUserImage>
<div *ngIf="!hasEcmUserAvatarId()" <div *ngIf="bpmUser$ | async as bpmUser" id="bpm-user-image">
[outerHTML]="ecmUser | usernameInitials:'adf-userinfo-pic'"></div> <div *ngIf="bpmUser.pictureId; else initialTemplate" class="adf-userinfo-profile-container">
<div *ngIf="hasEcmUserAvatarId()" class="adf-userinfo-profile-container"> <img id="logged-user-img" [src]="getBpmUserImage()" alt="user-info-profile-button"
<img id="logged-user-img" [src]="ecmUserImage" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/> class="adf-userinfo-profile-image"/>
</div> </div>
<ng-template #initialTemplate>
<div [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div> </div>
</ng-template>
</ng-template>
</div> </div>
</button> </button>
<mat-menu #menu="matMenu" id="user-profile-lists" [xPosition]="menuPositionX" [yPosition]="menuPositionY" [overlapTrigger]="false" class="adf-userinfo-menu"> <mat-menu #menu="matMenu" id="user-profile-lists" [xPosition]="menuPositionX" [yPosition]="menuPositionY" [overlapTrigger]="false" class="adf-userinfo-menu">
<mat-tab-group id="tab-group-env" (click)="stopClosing($event)" <mat-tab-group id="tab-group-env" (click)="stopClosing($event)"
class="adf-userinfo-tab" [class.adf-hide-tab]="!bpmUser || !ecmUser"> class="adf-userinfo-tab" [class.adf-hide-tab]="!(bpmUser$ | async) || !(ecmUser$ | async)">
<mat-tab id="ecm-panel" label="{{ 'USER_PROFILE.TAB.CS' | translate }}" *ngIf="ecmUser"> <mat-tab id="ecm-panel" label="{{ 'USER_PROFILE.TAB.CS' | translate }}" *ngIf="ecmUser$ | async as ecmUser">
<mat-card class="adf-userinfo-card"> <mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header" [style.background-image]="'url(' + ecmBackgroundImage + ')'"> <mat-card-header class="adf-userinfo-card-header" [style.background-image]="'url(' + ecmBackgroundImage + ')'">
<div *ngIf="!hasEcmUserAvatarId()" <div *ngIf="ecmUser.avatarId; else initialTemplate" class="adf-userinfo-profile-container adf-hide-small">
[outerHTML]="ecmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'">
</div>
<div *ngIf="hasEcmUserAvatarId()" class="adf-userinfo-profile-container adf-hide-small">
<img class="adf-userinfo-profile-picture" id="ecm-user-detail-image" <img class="adf-userinfo-profile-picture" id="ecm-user-detail-image"
alt="ecm-profile-image" [src]="ecmUserImage" /> alt="ecm-profile-image" [src]="getEcmAvatar(ecmUser.avatarId)" />
</div> </div>
<div class="adf-userinfo-title" id="ecm-username">{{ecmUser.fullNameDisplay}}</div> <ng-template #initialTemplate>
<div [outerHTML]="ecmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="ecm-username">{{ecmUser | fullName}}</div>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<div class="adf-userinfo-supporting-text"> <div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail"> <div class="adf-userinfo-detail">
<span id="ecm-full-name" class="adf-userinfo__detail-title">{{ecmUser.fullNameDisplay}}</span> <span id="ecm-full-name" class="adf-userinfo__detail-title">{{ecmUser | fullName}}</span>
<span class="adf-userinfo__detail-profile" id="ecm-email"> {{ecmUser.email}} </span> <span class="adf-userinfo__detail-profile" id="ecm-email"> {{ecmUser.email}} </span>
</div> </div>
<div class="adf-userinfo-detail"> <div class="adf-userinfo-detail">
@@ -55,18 +75,20 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</mat-tab> </mat-tab>
<mat-tab id="bpm-panel" label="{{ 'USER_PROFILE.TAB.PS' | translate }}" *ngIf="bpmUser"> <mat-tab id="bpm-panel" label="{{ 'USER_PROFILE.TAB.PS' | translate }}" *ngIf="bpmUser$ | async as bpmUser">
<mat-card class="adf-userinfo-card"> <mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header" [style.background-image]="'url(' + bpmBackgroundImage + ')'"> <mat-card-header class="adf-userinfo-card-header" [style.background-image]="'url(' + bpmBackgroundImage + ')'">
<div *ngIf="!hasBpmUserPictureId()" [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div> <img *ngIf="bpmUser.pictureId; else initialTemplate" class="adf-userinfo-profile-picture adf-hide-small" id="bpm-user-detail-image"
<img *ngIf="hasBpmUserPictureId()" class="adf-userinfo-profile-picture adf-hide-small" id="bpm-user-detail-image" alt="bpm-profile-image" [src]="getBpmUserImage()"/>
alt="bpm-profile-image" [src]="bpmUserImage"/> <ng-template #initialTemplate>
<div class="adf-userinfo-title" id="bpm-username">{{bpmUser.fullNameDisplay}}</div> <div [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="bpm-username">{{bpmUser | fullName}}</div>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<div class="adf-userinfo-supporting-text"> <div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail"> <div class="adf-userinfo-detail">
<span id="bpm-full-name" class="adf-userinfo__detail-title">{{ bpmUser.fullNameDisplay }}</span> <span id="bpm-full-name" class="adf-userinfo__detail-title">{{ bpmUser | fullName }}</span>
<span class="adf-userinfo__detail-profile" id="bpm-email"> {{bpmUser.email}} </span> <span class="adf-userinfo__detail-profile" id="bpm-email"> {{bpmUser.email}} </span>
</div> </div>
<div class="adf-userinfo-detail"> <div class="adf-userinfo-detail">
@@ -79,6 +101,24 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</mat-tab> </mat-tab>
<mat-tab id="identity-panel" *ngIf="identityUser$ | async as identityUser">
<mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header" [style.background-image]="'url(' + bpmBackgroundImage + ')'">
<div
[outerHTML]="identityUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'">
</div>
<div class="adf-userinfo-title" id="identity-username">{{identityUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="identity-full-name" class="adf-userinfo__detail-title">{{identityUser | fullName}}</span>
<span class="adf-userinfo__detail-profile" id="identity-email"> {{identityUser.email}} </span>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
</mat-tab-group> </mat-tab-group>
</mat-menu> </mat-menu>
</div> </div>

View File

@@ -23,11 +23,14 @@ import { fakeBpmUser } from '../../mock/bpm-user.service.mock';
import { fakeEcmEditedUser, fakeEcmUser, fakeEcmUserNoImage } from '../../mock/ecm-user.service.mock'; import { fakeEcmEditedUser, fakeEcmUser, fakeEcmUserNoImage } from '../../mock/ecm-user.service.mock';
import { BpmUserService } from '../services/bpm-user.service'; import { BpmUserService } from '../services/bpm-user.service';
import { EcmUserService } from '../services/ecm-user.service'; import { EcmUserService } from '../services/ecm-user.service';
import { IdentityUserService } from '../services/identity-user.service';
import { BpmUserModel } from './../models/bpm-user.model'; import { BpmUserModel } from './../models/bpm-user.model';
import { EcmUserModel } from './../models/ecm-user.model';
import { UserInfoComponent } from './user-info.component'; import { UserInfoComponent } from './user-info.component';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { setupTestBed } from '../../testing/setupTestBed'; import { setupTestBed } from '../../testing/setupTestBed';
import { CoreTestingModule } from '../../testing/core.testing.module'; import { CoreTestingModule } from '../../testing/core.testing.module';
import { IdentityUserModel } from '../models/identity-user.model';
class FakeSanitizer extends DomSanitizer { class FakeSanitizer extends DomSanitizer {
@@ -69,6 +72,11 @@ describe('User info component', () => {
let contentService: ContentService; let contentService: ContentService;
let ecmUserService: EcmUserService; let ecmUserService: EcmUserService;
let bpmUserService: BpmUserService; let bpmUserService: BpmUserService;
let identityUserService: IdentityUserService;
let identityUserMock = { firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
let identityUserWithOutFirstNameMock = { firstName: null, lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
let identityUserWithOutLastNameMock = { firstName: 'fake-identity-first-name', lastName: null, email: 'fakeIdentity@email.com' };
function openUserInfo() { function openUserInfo() {
fixture.detectChanges(); fixture.detectChanges();
@@ -90,6 +98,7 @@ describe('User info component', () => {
ecmUserService = TestBed.get(EcmUserService); ecmUserService = TestBed.get(EcmUserService);
bpmUserService = TestBed.get(BpmUserService); bpmUserService = TestBed.get(BpmUserService);
contentService = TestBed.get(ContentService); contentService = TestBed.get(ContentService);
identityUserService = TestBed.get(IdentityUserService);
})); }));
afterEach(() => { afterEach(() => {
@@ -113,11 +122,25 @@ describe('User info component', () => {
describe('ui ', () => { describe('ui ', () => {
beforeEach(() => { beforeEach(() => {
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(authService, 'isEcmLoggedIn').and.returnValue(true); spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
spyOn(authService, 'isLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true);
spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmEditedUser)); spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmEditedUser));
fixture.detectChanges();
}); });
it('should able to fetch ecm userInfo', async(() => {
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');
});
});
}));
it('should show ecm only last name when user first name is null ', async(() => { it('should show ecm only last name when user first name is null ', async(() => {
fixture.detectChanges(); fixture.detectChanges();
@@ -176,6 +199,7 @@ describe('User info component', () => {
describe('and has image', () => { describe('and has image', () => {
beforeEach(async(() => { beforeEach(async(() => {
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(authService, 'isEcmLoggedIn').and.returnValue(true); spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
spyOn(authService, 'isLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true);
spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmUser)); spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmUser));
@@ -205,7 +229,10 @@ describe('User info component', () => {
imageButton.click(); imageButton.click();
fixture.detectChanges(); fixture.detectChanges();
let loggedImage = fixture.debugElement.query(By.css('#logged-user-img')); let loggedImage = fixture.debugElement.query(By.css('#logged-user-img'));
expect(component.ecmUser.avatarId).toBe('fake-avatar-id'); component.ecmUser$.subscribe((response: EcmUserModel) => {
expect(response).toBeDefined();
expect(response.avatarId).toBe('fake-avatar-id');
});
expect(element.querySelector('#userinfo_container')).not.toBeNull(); expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(loggedImage).not.toBeNull(); expect(loggedImage).not.toBeNull();
expect(loggedImage.properties.src).toContain('assets/images/ecmImg.gif'); expect(loggedImage.properties.src).toContain('assets/images/ecmImg.gif');
@@ -236,6 +263,7 @@ describe('User info component', () => {
describe('and has no image', () => { describe('and has no image', () => {
beforeEach(async(() => { beforeEach(async(() => {
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(authService, 'isEcmLoggedIn').and.returnValue(true); spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
spyOn(authService, 'isLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true);
spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmUserNoImage)); spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmUserNoImage));
@@ -266,7 +294,10 @@ describe('User info component', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
let pipe = new InitialUsernamePipe(new FakeSanitizer()); let pipe = new InitialUsernamePipe(new FakeSanitizer());
expect(component.ecmUser.avatarId).toBeNull(); component.ecmUser$.subscribe((response: EcmUserModel) => {
expect(response).toBeDefined();
expect(response.avatarId).toBeNull();
});
expect(pipe.transform({ expect(pipe.transform({
id: 13, id: 13,
firstName: 'Wilbur', firstName: 'Wilbur',
@@ -284,12 +315,27 @@ describe('User info component', () => {
let getCurrentUserInfoStub; let getCurrentUserInfoStub;
beforeEach(async(() => { beforeEach(async(() => {
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(authService, 'isBpmLoggedIn').and.returnValue(true); spyOn(authService, 'isBpmLoggedIn').and.returnValue(true);
spyOn(authService, 'isLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true);
getCurrentUserInfoStub = spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser)); getCurrentUserInfoStub = spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser));
})); }));
it('should fetch bpm userInfo', async(() => {
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');
});
});
}));
it('should show full name next the user image', async(() => { it('should show full name next the user image', async(() => {
getCurrentUserInfoStub.and.returnValue(of(fakeBpmUser));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@@ -305,6 +351,7 @@ describe('User info component', () => {
})); }));
it('should get the bpm current user image from the service', async(() => { it('should get the bpm current user image from the service', async(() => {
getCurrentUserInfoStub.and.returnValue(of(fakeBpmUser));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@@ -316,18 +363,20 @@ describe('User info component', () => {
})); }));
it('should show last name if first name is null', async(() => { it('should show last name if first name is null', async(() => {
fixture.detectChanges();
let wrongBpmUser: BpmUserModel = new BpmUserModel({ let wrongBpmUser: BpmUserModel = new BpmUserModel({
firstName: null, firstName: null,
lastName: 'fake-last-name' lastName: 'fake-last-name'
}); });
getCurrentUserInfoStub.and.returnValue(of(wrongBpmUser)); getCurrentUserInfoStub.and.returnValue(of(wrongBpmUser));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const fullNameElement = (element.querySelector('#adf-userinfo-bpm-name-display'));
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined(); expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#adf-userinfo-bpm-name-display')).not.toBeNull(); expect(element.querySelector('#adf-userinfo-bpm-name-display')).not.toBeNull();
expect(element.querySelector('#adf-userinfo-bpm-name-display').textContent).toContain('fake-last-name'); expect(fullNameElement.textContent).toContain('fake-last-name');
expect(element.querySelector('#adf-userinfo-bpm-name-display').textContent).not.toContain('fake-bpm-first-name'); expect(fullNameElement.textContent).not.toContain('fake-first-name');
}); });
})); }));
@@ -383,6 +432,7 @@ describe('User info component', () => {
let ecmUserInfoSpy; let ecmUserInfoSpy;
beforeEach(async(() => { beforeEach(async(() => {
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(authService, 'isEcmLoggedIn').and.returnValue(true); spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
spyOn(authService, 'isBpmLoggedIn').and.returnValue(true); spyOn(authService, 'isBpmLoggedIn').and.returnValue(true);
spyOn(authService, 'isLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true);
@@ -392,6 +442,32 @@ describe('User info component', () => {
spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser)); spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser));
})); }));
it('should able to fetch ecm userInfo', async(() => {
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');
});
});
}));
it('should able to fetch bpm userInfo', async(() => {
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');
});
});
}));
it('should get the bpm user informations from the service', async(() => { it('should get the bpm user informations from the service', async(() => {
openUserInfo(); openUserInfo();
let bpmTab = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'))[1]; let bpmTab = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'))[1];
@@ -439,7 +515,6 @@ describe('User info component', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined(); expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(element.querySelector('#logged-user-img')).toBeNull();
expect(element.querySelector('#user-initials-image').textContent).toContain('ff'); expect(element.querySelector('#user-initials-image').textContent).toContain('ff');
}); });
})); }));
@@ -465,4 +540,90 @@ describe('User info component', () => {
expect(fixture.debugElement.query(By.css('#user-profile-lists'))).not.toBeNull(); expect(fixture.debugElement.query(By.css('#user-profile-lists'))).not.toBeNull();
}); });
}); });
describe('when identity user is logged in', () => {
let getCurrentUserInfoStub;
beforeEach(async(() => {
spyOn(authService, 'isOauth').and.returnValue(true);
spyOn(authService, 'isLoggedIn').and.returnValue(true);
getCurrentUserInfoStub = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(of(identityUserMock));
}));
it('should able to fetch identity userInfo', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
component.identityUser$.subscribe((response: IdentityUserModel) => {
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');
});
});
}));
it('should show full name next the user image', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let imageButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#identity-user-image');
imageButton.click();
fixture.detectChanges();
let bpmUserName = element.querySelector('#identity-username');
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).not.toBeNull();
expect(bpmUserName).toBeDefined();
expect(bpmUserName).not.toBeNull();
expect(bpmUserName.textContent).toContain('fake-identity-first-name fake-identity-last-name');
});
}));
it('should show last name if first name is null', async(() => {
fixture.detectChanges();
let fakeIdentityUser: IdentityUserModel = new IdentityUserModel(identityUserWithOutFirstNameMock);
getCurrentUserInfoStub.and.returnValue(of(fakeIdentityUser));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
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(() => {
let fakeIdentityUser: IdentityUserModel = new IdentityUserModel(identityUserWithOutFirstNameMock);
getCurrentUserInfoStub.and.returnValue(of(fakeIdentityUser));
fixture.detectChanges();
fixture.whenStable().then(() => {
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(() => {
let fakeIdentityUser: IdentityUserModel = new IdentityUserModel(identityUserWithOutLastNameMock);
getCurrentUserInfoStub.and.returnValue(of(fakeIdentityUser));
fixture.detectChanges();
fixture.whenStable().then(() => {
const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display');
fixture.detectChanges();
expect(element.querySelector('#userinfo_container')).toBeDefined();
expect(fullNameElement).toBeDefined();
expect(fullNameElement.textContent).toContain('fake-identity-first-name');
expect(fullNameElement.textContent).not.toContain('null');
});
}));
});
}); });

View File

@@ -19,8 +19,11 @@ import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { AuthenticationService } from '../../services/authentication.service'; import { AuthenticationService } from '../../services/authentication.service';
import { BpmUserModel } from './../models/bpm-user.model'; import { BpmUserModel } from './../models/bpm-user.model';
import { EcmUserModel } from './../models/ecm-user.model'; import { EcmUserModel } from './../models/ecm-user.model';
import { IdentityUserModel } from './../models/identity-user.model';
import { BpmUserService } from './../services/bpm-user.service'; import { BpmUserService } from './../services/bpm-user.service';
import { EcmUserService } from './../services/ecm-user.service'; import { EcmUserService } from './../services/ecm-user.service';
import { IdentityUserService } from '../services/identity-user.service';
import { Observable } from 'rxjs';
@Component({ @Component({
selector: 'adf-userinfo', selector: 'adf-userinfo',
@@ -56,14 +59,14 @@ export class UserInfoComponent implements OnInit {
@Input() @Input()
namePosition: string = 'right'; namePosition: string = 'right';
ecmUser: EcmUserModel; ecmUser$: Observable<EcmUserModel>;
bpmUser: BpmUserModel; bpmUser$: Observable<BpmUserModel>;
bpmUserImage: any; identityUser$: Observable<IdentityUserModel>;
ecmUserImage: any;
selectedIndex: number; selectedIndex: number;
constructor(private ecmUserService: EcmUserService, constructor(private ecmUserService: EcmUserService,
private bpmUserService: BpmUserService, private bpmUserService: BpmUserService,
private identityUserService: IdentityUserService,
private authService: AuthenticationService) { private authService: AuthenticationService) {
} }
@@ -72,58 +75,47 @@ export class UserInfoComponent implements OnInit {
} }
getUserInfo() { getUserInfo() {
if (this.authService.isOauth()) {
this.loadIdentityUserInfo();
} else if (this.authService.isEcmLoggedIn() && this.authService.isBpmLoggedIn()) {
this.loadEcmUserInfo(); this.loadEcmUserInfo();
this.loadBpmUserInfo(); this.loadBpmUserInfo();
} else if (this.authService.isEcmLoggedIn()) {
this.loadEcmUserInfo();
} else if (this.authService.isBpmLoggedIn()) {
this.loadBpmUserInfo();
}
} }
isLoggedIn() { isLoggedIn(): boolean {
return this.authService.isLoggedIn(); return this.authService.isLoggedIn();
} }
loadEcmUserInfo(): void { loadEcmUserInfo(): void {
if (this.authService.isEcmLoggedIn()) { this.ecmUser$ = this.ecmUserService.getCurrentUserInfo();
this.ecmUserService.getCurrentUserInfo()
.subscribe((res) => {
this.ecmUser = new EcmUserModel(res);
this.getEcmAvatar();
});
} else {
this.ecmUser = null;
this.ecmUserImage = null;
}
} }
loadBpmUserInfo(): void { loadBpmUserInfo() {
if (this.authService.isBpmLoggedIn()) { this.bpmUser$ = this.bpmUserService.getCurrentUserInfo();
this.bpmUserService.getCurrentUserInfo()
.subscribe((res) => {
this.bpmUser = new BpmUserModel(res);
});
this.bpmUserImage = this.bpmUserService.getCurrentUserProfileImage();
} else {
this.bpmUser = null;
this.bpmUserImage = null;
} }
loadIdentityUserInfo() {
this.identityUser$ = this.identityUserService.getCurrentUserInfo();
} }
stopClosing(event) { stopClosing(event) {
event.stopPropagation(); event.stopPropagation();
} }
private getEcmAvatar() { getEcmAvatar(avatarId: any ): string {
this.ecmUserImage = this.ecmUserService.getUserProfileImage(this.ecmUser.avatarId); return this.ecmUserService.getUserProfileImage(avatarId);
} }
showOnRight() { getBpmUserImage(): string {
return this.bpmUserService.getCurrentUserProfileImage();
}
showOnRight(): boolean {
return this.namePosition === 'right'; return this.namePosition === 'right';
} }
hasBpmUserPictureId(): boolean {
return !!this.bpmUser.pictureId;
}
hasEcmUserAvatarId(): boolean {
return !!this.ecmUser.avatarId;
}
} }

View File

@@ -27,7 +27,6 @@ export class BpmUserModel implements UserRepresentation {
firstName: string; firstName: string;
lastName: string; lastName: string;
fullname: string; fullname: string;
fullNameDisplay: string;
groups: any; groups: any;
id: number; id: number;
lastUpdate: Date; lastUpdate: Date;
@@ -51,7 +50,6 @@ export class BpmUserModel implements UserRepresentation {
this.firstName = obj.firstName; this.firstName = obj.firstName;
this.lastName = obj.lastName; this.lastName = obj.lastName;
this.fullname = obj.fullname; this.fullname = obj.fullname;
this.fullNameDisplay = obj ? this.formatValue(obj.firstName).trim() + ' ' + this.formatValue(obj.lastName).trim() : null;
this.groups = obj.groups; this.groups = obj.groups;
this.id = obj.id; this.id = obj.id;
this.lastUpdate = obj.lastUpdate; this.lastUpdate = obj.lastUpdate;
@@ -65,8 +63,4 @@ export class BpmUserModel implements UserRepresentation {
this.type = obj.type; this.type = obj.type;
} }
} }
private formatValue(value: string): string {
return value && value !== 'null' ? value : '';
}
} }

View File

@@ -22,7 +22,6 @@ export class EcmUserModel implements Person {
id: string; id: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
fullNameDisplay: string;
description: string; description: string;
avatarId: string; avatarId: string;
email: string; email: string;
@@ -43,7 +42,6 @@ export class EcmUserModel implements Person {
this.id = obj && obj.id || null; this.id = obj && obj.id || null;
this.firstName = obj && obj.firstName; this.firstName = obj && obj.firstName;
this.lastName = obj && obj.lastName; this.lastName = obj && obj.lastName;
this.fullNameDisplay = obj ? this.formatValue(obj.firstName).trim() + ' ' + this.formatValue(obj.lastName).trim() : null;
this.description = obj && obj.description || null; this.description = obj && obj.description || null;
this.avatarId = obj && obj.avatarId || null; this.avatarId = obj && obj.avatarId || null;
this.email = obj && obj.email || null; this.email = obj && obj.email || null;
@@ -60,8 +58,4 @@ export class EcmUserModel implements Person {
this.enabled = obj && obj.enabled; this.enabled = obj && obj.enabled;
this.emailNotificationsEnabled = obj && obj.emailNotificationsEnabled; this.emailNotificationsEnabled = obj && obj.emailNotificationsEnabled;
} }
private formatValue(value: string) {
return value && value !== 'null' ? value : '';
}
} }

View File

@@ -0,0 +1,31 @@
/*!
* @license
* Copyright 2016 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 class IdentityUserModel {
firstName: string;
lastName: string;
email: string;
constructor(obj?: any) {
if (obj) {
this.firstName = obj.firstName || null;
this.lastName = obj.lastName || null;
this.email = obj.email || null;
}
}
}

View File

@@ -18,5 +18,9 @@
export * from './components/user-info.component'; export * from './components/user-info.component';
export * from './services/bpm-user.service'; export * from './services/bpm-user.service';
export * from './services/ecm-user.service'; export * from './services/ecm-user.service';
export * from './services/identity-user.service';
export * from './models/bpm-user.model';
export * from './models/ecm-user.model';
export * from './models/identity-user.model';
export * from './userinfo.module'; export * from './userinfo.module';

View File

@@ -22,6 +22,7 @@ import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service'; import { LogService } from '../../services/log.service';
import { BpmUserModel } from '../models/bpm-user.model'; import { BpmUserModel } from '../models/bpm-user.model';
import { map, catchError } from 'rxjs/operators'; import { map, catchError } from 'rxjs/operators';
import { UserRepresentation } from 'alfresco-js-api';
/** /**
* *
@@ -44,7 +45,9 @@ export class BpmUserService {
getCurrentUserInfo(): Observable<BpmUserModel> { getCurrentUserInfo(): Observable<BpmUserModel> {
return from(this.apiService.getInstance().activiti.profileApi.getProfile()) return from(this.apiService.getInstance().activiti.profileApi.getProfile())
.pipe( .pipe(
map((data) => <BpmUserModel> data), map((data: UserRepresentation) => {
return new BpmUserModel(data);
}),
catchError(err => this.handleError(err)) catchError(err => this.handleError(err))
); );
} }

View File

@@ -23,6 +23,7 @@ import { ContentService } from '../../services/content.service';
import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service'; import { LogService } from '../../services/log.service';
import { EcmUserModel } from '../models/ecm-user.model'; import { EcmUserModel } from '../models/ecm-user.model';
import { PersonEntry } from 'alfresco-js-api';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -42,7 +43,9 @@ export class EcmUserService {
getUserInfo(userName: string): Observable<EcmUserModel> { getUserInfo(userName: string): Observable<EcmUserModel> {
return from(this.apiService.getInstance().core.peopleApi.getPerson(userName)) return from(this.apiService.getInstance().core.peopleApi.getPerson(userName))
.pipe( .pipe(
map(data => <EcmUserModel> data['entry']), map((personEntry: PersonEntry) => {
return new EcmUserModel(personEntry.entry);
}),
catchError(err => this.handleError(err)) catchError(err => this.handleError(err))
); );
} }

View File

@@ -0,0 +1,60 @@
/*!
* @license
* Copyright 2016 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 { TestBed } from '@angular/core/testing';
import { IdentityUserService } from '../services/identity-user.service';
import { setupTestBed } from '../../testing/setupTestBed';
import { CoreModule } from '../../core.module';
import { mockToken } from './../../mock/jwt-helper.service.spec';
describe('IdentityUserService', () => {
let service: IdentityUserService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => {
service = TestBed.get(IdentityUserService);
});
beforeEach(() => {
let store = {};
spyOn(localStorage, 'getItem').and.callFake( (key: string): String => {
return store[key] || null;
});
spyOn(localStorage, 'setItem').and.callFake((key: string, value: string): string => {
return store[key] = <string> value;
});
});
it('should able to fetch identity user info from Jwt token', (done) => {
localStorage.setItem('access_token', mockToken);
service.getCurrentUserInfo().subscribe(
(user) => {
expect(user).toBeDefined();
expect(user.firstName).toEqual('John');
expect(user.lastName).toEqual('Doe');
expect(user.email).toEqual('johnDoe@gmail.com');
done();
});
});
});

View File

@@ -0,0 +1,51 @@
/*!
* @license
* Copyright 2016 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 { Injectable } from '@angular/core';
import { of, Observable } from 'rxjs';
import { IdentityUserModel } from '../models/identity-user.model';
import { JwtHelperService } from './../../services/jwt-helper.service';
@Injectable({
providedIn: 'root'
})
export class IdentityUserService {
static USER_NAME = 'given_name';
static USER_EMAIL = 'email';
static USER_ACCESS_TOKEN = 'access_token';
constructor(private helper: JwtHelperService) {}
getCurrentUserInfo(): Observable<IdentityUserModel> {
const fullName = this.getValueFromToken<string>(IdentityUserService.USER_NAME);
const email = this.getValueFromToken<string>(IdentityUserService.USER_EMAIL);
const nameParts = fullName.split(' ');
const user = { firstName: nameParts[0], lastName: nameParts[1], email: email };
return of(new IdentityUserModel(user));
}
getValueFromToken<T>(key: string): T {
let value;
const token = localStorage.getItem(IdentityUserService.USER_ACCESS_TOKEN);
if (token) {
const tokenPayload = this.helper.decodeToken(token);
value = tokenPayload[key];
}
return <T> value;
}
}

124
package-lock.json generated
View File

@@ -787,18 +787,18 @@
"resolved": "https://registry.npmjs.org/@nrwl/schematics/-/schematics-6.3.1.tgz", "resolved": "https://registry.npmjs.org/@nrwl/schematics/-/schematics-6.3.1.tgz",
"integrity": "sha512-Dy4D6RaD+4BZFMfRr+5QaE9J5AjZ7UlldIpsyWvh6VofR9JseVpbetsvcSUsTT3fpjmORIHGMQM7Kpp+gnHaeA==", "integrity": "sha512-Dy4D6RaD+4BZFMfRr+5QaE9J5AjZ7UlldIpsyWvh6VofR9JseVpbetsvcSUsTT3fpjmORIHGMQM7Kpp+gnHaeA==",
"requires": { "requires": {
"@types/yargs": "11.1.2", "@types/yargs": "^11.0.0",
"app-root-path": "2.0.1", "app-root-path": "^2.0.1",
"cosmiconfig": "4.0.0", "cosmiconfig": "4.0.0",
"fs-extra": "6.0.0", "fs-extra": "6.0.0",
"graphviz": "0.0.8", "graphviz": "0.0.8",
"npm-run-all": "4.1.2", "npm-run-all": "4.1.2",
"opn": "5.4.0", "opn": "^5.3.0",
"semver": "5.4.1", "semver": "5.4.1",
"strip-json-comments": "2.0.1", "strip-json-comments": "2.0.1",
"tmp": "0.0.33", "tmp": "0.0.33",
"viz.js": "1.8.2", "viz.js": "^1.8.1",
"yargs": "11.1.0", "yargs": "^11.0.0",
"yargs-parser": "10.0.0" "yargs-parser": "10.0.0"
}, },
"dependencies": { "dependencies": {
@@ -807,9 +807,9 @@
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.0.tgz",
"integrity": "sha512-lk2cUCo8QzbiEWEbt7Cw3m27WMiRG321xsssbcIpfMhpRjrlC08WBOVQqj1/nQYYNnPtyIhP1oqLO3QwT2tPCw==", "integrity": "sha512-lk2cUCo8QzbiEWEbt7Cw3m27WMiRG321xsssbcIpfMhpRjrlC08WBOVQqj1/nQYYNnPtyIhP1oqLO3QwT2tPCw==",
"requires": { "requires": {
"graceful-fs": "4.1.11", "graceful-fs": "^4.1.2",
"jsonfile": "4.0.0", "jsonfile": "^4.0.0",
"universalify": "0.1.2" "universalify": "^0.1.0"
} }
}, },
"semver": { "semver": {
@@ -822,7 +822,7 @@
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"requires": { "requires": {
"os-tmpdir": "1.0.2" "os-tmpdir": "~1.0.2"
} }
}, },
"yargs-parser": { "yargs-parser": {
@@ -830,7 +830,7 @@
"resolved": "http://registry.npmjs.org/yargs-parser/-/yargs-parser-10.0.0.tgz", "resolved": "http://registry.npmjs.org/yargs-parser/-/yargs-parser-10.0.0.tgz",
"integrity": "sha512-+DHejWujTVYeMHLff8U96rLc4uE4Emncoftvn5AjhB1Jw1pWxLzgBUT/WYbPrHmy6YPEBTZQx5myHhVcuuu64g==", "integrity": "sha512-+DHejWujTVYeMHLff8U96rLc4uE4Emncoftvn5AjhB1Jw1pWxLzgBUT/WYbPrHmy6YPEBTZQx5myHhVcuuu64g==",
"requires": { "requires": {
"camelcase": "4.1.0" "camelcase": "^4.1.0"
} }
} }
} }
@@ -5731,14 +5731,14 @@
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz",
"integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==",
"requires": { "requires": {
"duplexer": "0.1.1", "duplexer": "^0.1.1",
"flatmap-stream": "0.1.1", "flatmap-stream": "^0.1.0",
"from": "0.1.7", "from": "^0.1.7",
"map-stream": "0.0.7", "map-stream": "0.0.7",
"pause-stream": "0.0.11", "pause-stream": "^0.0.11",
"split": "1.0.1", "split": "^1.0.1",
"stream-combiner": "0.2.2", "stream-combiner": "^0.2.2",
"through": "2.3.8" "through": "^2.3.8"
} }
}, },
"eventemitter3": { "eventemitter3": {
@@ -6559,12 +6559,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -6579,17 +6581,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -6706,7 +6711,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -6718,6 +6724,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -6732,6 +6739,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -6739,12 +6747,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -6763,6 +6773,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -6843,7 +6854,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -6855,6 +6867,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -6976,6 +6989,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -7487,7 +7501,7 @@
"resolved": "https://registry.npmjs.org/graphviz/-/graphviz-0.0.8.tgz", "resolved": "https://registry.npmjs.org/graphviz/-/graphviz-0.0.8.tgz",
"integrity": "sha1-5ZnkBzPvgOFlO/6JpfAx7PKqSqo=", "integrity": "sha1-5ZnkBzPvgOFlO/6JpfAx7PKqSqo=",
"requires": { "requires": {
"temp": "0.4.0" "temp": "~0.4.0"
} }
}, },
"gray-matter": { "gray-matter": {
@@ -12043,15 +12057,15 @@
"resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.2.tgz", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.2.tgz",
"integrity": "sha512-Z2aRlajMK4SQ8u19ZA75NZZu7wupfCNQWdYosIi8S6FgBdGf/8Y6Hgyjdc8zU2cYmIRVCx1nM80tJPkdEd+UYg==", "integrity": "sha512-Z2aRlajMK4SQ8u19ZA75NZZu7wupfCNQWdYosIi8S6FgBdGf/8Y6Hgyjdc8zU2cYmIRVCx1nM80tJPkdEd+UYg==",
"requires": { "requires": {
"ansi-styles": "3.2.1", "ansi-styles": "^3.2.0",
"chalk": "2.4.1", "chalk": "^2.1.0",
"cross-spawn": "5.1.0", "cross-spawn": "^5.1.0",
"memorystream": "0.3.1", "memorystream": "^0.3.1",
"minimatch": "3.0.4", "minimatch": "^3.0.4",
"ps-tree": "1.1.0", "ps-tree": "^1.1.0",
"read-pkg": "3.0.0", "read-pkg": "^3.0.0",
"shell-quote": "1.6.1", "shell-quote": "^1.6.1",
"string.prototype.padend": "3.0.0" "string.prototype.padend": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"load-json-file": { "load-json-file": {
@@ -12059,10 +12073,10 @@
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
"requires": { "requires": {
"graceful-fs": "4.1.11", "graceful-fs": "^4.1.2",
"parse-json": "4.0.0", "parse-json": "^4.0.0",
"pify": "3.0.0", "pify": "^3.0.0",
"strip-bom": "3.0.0" "strip-bom": "^3.0.0"
} }
}, },
"parse-json": { "parse-json": {
@@ -12070,8 +12084,8 @@
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"requires": { "requires": {
"error-ex": "1.3.2", "error-ex": "^1.3.1",
"json-parse-better-errors": "1.0.2" "json-parse-better-errors": "^1.0.1"
} }
}, },
"read-pkg": { "read-pkg": {
@@ -12079,9 +12093,9 @@
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
"integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
"requires": { "requires": {
"load-json-file": "4.0.0", "load-json-file": "^4.0.0",
"normalize-package-data": "2.4.0", "normalize-package-data": "^2.3.2",
"path-type": "3.0.0" "path-type": "^3.0.0"
} }
}, },
"strip-bom": { "strip-bom": {
@@ -12763,7 +12777,7 @@
"resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
"integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
"requires": { "requires": {
"through": "2.3.8" "through": "~2.3"
} }
}, },
"pbkdf2": { "pbkdf2": {
@@ -13364,7 +13378,7 @@
"resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz",
"integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=",
"requires": { "requires": {
"event-stream": "3.3.6" "event-stream": "~3.3.0"
} }
}, },
"pseudomap": { "pseudomap": {
@@ -14978,10 +14992,10 @@
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz",
"integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=",
"requires": { "requires": {
"array-filter": "0.0.1", "array-filter": "~0.0.0",
"array-map": "0.0.0", "array-map": "~0.0.0",
"array-reduce": "0.0.0", "array-reduce": "~0.0.0",
"jsonify": "0.0.0" "jsonify": "~0.0.0"
} }
}, },
"shelljs": { "shelljs": {
@@ -15523,7 +15537,7 @@
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
"integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
"requires": { "requires": {
"through": "2.3.8" "through": "2"
} }
}, },
"split-string": { "split-string": {
@@ -15638,8 +15652,8 @@
"resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
"integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
"requires": { "requires": {
"duplexer": "0.1.1", "duplexer": "~0.1.1",
"through": "2.3.8" "through": "~2.3.4"
} }
}, },
"stream-each": { "stream-each": {
@@ -15707,9 +15721,9 @@
"resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz",
"integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=",
"requires": { "requires": {
"define-properties": "1.1.3", "define-properties": "^1.1.2",
"es-abstract": "1.12.0", "es-abstract": "^1.4.3",
"function-bind": "1.1.1" "function-bind": "^1.0.2"
} }
}, },
"string_decoder": { "string_decoder": {