mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[ACS-8433] ACA: User Profile Service (#3957)
This commit is contained in:
parent
f23f5edd08
commit
b5568d43fa
9
.github/workflows/pull-request.yml
vendored
9
.github/workflows/pull-request.yml
vendored
@ -70,20 +70,13 @@ jobs:
|
|||||||
|
|
||||||
unit-tests:
|
unit-tests:
|
||||||
needs: [lint, build]
|
needs: [lint, build]
|
||||||
name: "Unit tests: ${{ matrix.unit-tests.name }}"
|
name: "Unit tests"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
unit-tests:
|
|
||||||
- name: "aca-content"
|
|
||||||
- name: "aca-shared"
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: node
|
- name: node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
|
@ -1,2 +1 @@
|
|||||||
<h1 class="aca-sr-only" title="{{pageHeading | async | translate}}">{{ pageHeading | async | translate }}</h1>
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, ViewEncapsulation } from '@angular/core';
|
import { Component, ViewEncapsulation } from '@angular/core';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { AppService } from '@alfresco/aca-shared';
|
import { AppService } from '@alfresco/aca-shared';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -34,10 +34,8 @@ import { AppService } from '@alfresco/aca-shared';
|
|||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||||
pageHeading: Observable<string>;
|
|
||||||
|
|
||||||
constructor(private appService: AppService) {
|
constructor(private appService: AppService) {
|
||||||
this.pageHeading = this.appService.pageHeading$;
|
|
||||||
this.appService.init();
|
this.appService.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ module.exports = () => {
|
|||||||
check: {
|
check: {
|
||||||
global: {
|
global: {
|
||||||
statements: 75,
|
statements: 75,
|
||||||
branches: 67,
|
branches: 65,
|
||||||
functions: 71,
|
functions: 71,
|
||||||
lines: 74
|
lines: 74
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { AppStore, getUserProfile } from '@alfresco/aca-shared/store';
|
import { UserProfileService } from '@alfresco/aca-shared';
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@ -39,6 +38,7 @@ import { Store } from '@ngrx/store';
|
|||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class UserInfoComponent {
|
export class UserInfoComponent {
|
||||||
private store = inject<Store<AppStore>>(Store<AppStore>);
|
private userProfileService = inject(UserProfileService);
|
||||||
user$ = this.store.select(getUserProfile);
|
|
||||||
|
user$ = this.userProfileService.userProfile$;
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { ToolbarMenuItemComponent } from '@alfresco/aca-shared';
|
import { ToolbarMenuItemComponent, UserProfileService } from '@alfresco/aca-shared';
|
||||||
import { AppStore, getUserProfile } from '@alfresco/aca-shared/store';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@ -42,8 +40,9 @@ import { Store } from '@ngrx/store';
|
|||||||
host: { class: 'aca-user-menu' }
|
host: { class: 'aca-user-menu' }
|
||||||
})
|
})
|
||||||
export class UserMenuComponent implements OnInit {
|
export class UserMenuComponent implements OnInit {
|
||||||
private store = inject<Store<AppStore>>(Store<AppStore>);
|
private userProfileService = inject(UserProfileService);
|
||||||
user$ = this.store.select(getUserProfile);
|
|
||||||
|
user$ = this.userProfileService.userProfile$;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
actionRef: ContentActionRef;
|
actionRef: ContentActionRef;
|
||||||
|
@ -22,17 +22,10 @@
|
|||||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { AppStore, SetSelectedNodesAction, SnackbarErrorAction, SnackbarInfoAction, getAppSelection } from '@alfresco/aca-shared/store';
|
||||||
AppStore,
|
import { AppHookService, UserProfileService } from '@alfresco/aca-shared';
|
||||||
SetSelectedNodesAction,
|
import { SelectionState } from '@alfresco/adf-extensions';
|
||||||
SnackbarErrorAction,
|
import { Component, inject, ViewEncapsulation } from '@angular/core';
|
||||||
SnackbarInfoAction,
|
|
||||||
getAppSelection,
|
|
||||||
getUserProfile
|
|
||||||
} from '@alfresco/aca-shared/store';
|
|
||||||
import { AppHookService } from '@alfresco/aca-shared';
|
|
||||||
import { ProfileState, SelectionState } from '@alfresco/adf-extensions';
|
|
||||||
import { Component, ViewEncapsulation } from '@angular/core';
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { LibraryMembershipDirective, LibraryMembershipErrorEvent, LibraryMembershipToggleEvent } from '@alfresco/adf-content-services';
|
import { LibraryMembershipDirective, LibraryMembershipErrorEvent, LibraryMembershipToggleEvent } from '@alfresco/adf-content-services';
|
||||||
@ -64,12 +57,13 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
host: { class: 'app-toggle-join-library' }
|
host: { class: 'app-toggle-join-library' }
|
||||||
})
|
})
|
||||||
export class ToggleJoinLibraryButtonComponent {
|
export class ToggleJoinLibraryButtonComponent {
|
||||||
|
private userProfileService = inject(UserProfileService);
|
||||||
|
|
||||||
selection$: Observable<SelectionState>;
|
selection$: Observable<SelectionState>;
|
||||||
profile$: Observable<ProfileState>;
|
profile$ = this.userProfileService.userProfile$;
|
||||||
|
|
||||||
constructor(private store: Store<AppStore>, private appHookService: AppHookService) {
|
constructor(private store: Store<AppStore>, private appHookService: AppHookService) {
|
||||||
this.selection$ = this.store.select(getAppSelection);
|
this.selection$ = this.store.select(getAppSelection);
|
||||||
this.profile$ = this.store.select(getUserProfile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleEvent(event: LibraryMembershipToggleEvent) {
|
onToggleEvent(event: LibraryMembershipToggleEvent) {
|
||||||
|
@ -22,10 +22,16 @@
|
|||||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getUserProfile } from '@alfresco/aca-shared/store';
|
|
||||||
import { DocumentListPresetRef, DynamicColumnComponent } from '@alfresco/adf-extensions';
|
import { DocumentListPresetRef, DynamicColumnComponent } from '@alfresco/adf-extensions';
|
||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { ContextActionsDirective, PageComponent, PageLayoutComponent, PaginationDirective, ToolbarComponent } from '@alfresco/aca-shared';
|
import {
|
||||||
|
ContextActionsDirective,
|
||||||
|
PageComponent,
|
||||||
|
PageLayoutComponent,
|
||||||
|
PaginationDirective,
|
||||||
|
ToolbarComponent,
|
||||||
|
UserProfileService
|
||||||
|
} from '@alfresco/aca-shared';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { DocumentListModule } from '@alfresco/adf-content-services';
|
import { DocumentListModule } from '@alfresco/adf-content-services';
|
||||||
@ -52,7 +58,9 @@ import { DocumentListDirective } from '../../directives/document-list.directive'
|
|||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class TrashcanComponent extends PageComponent implements OnInit {
|
export class TrashcanComponent extends PageComponent implements OnInit {
|
||||||
user$ = this.store.select(getUserProfile);
|
private userProfileService = inject(UserProfileService);
|
||||||
|
|
||||||
|
user$ = this.userProfileService.userProfile$;
|
||||||
columns: DocumentListPresetRef[] = [];
|
columns: DocumentListPresetRef[] = [];
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -92,32 +92,7 @@ export function appReducer(state: AppState = INITIAL_APP_STATE, action: Action):
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateUser(state: AppState, action: SetUserProfileAction): AppState {
|
function updateUser(state: AppState, action: SetUserProfileAction): AppState {
|
||||||
const newState = { ...state };
|
return { ...state, user: { ...action.payload } };
|
||||||
const user = action.payload.person;
|
|
||||||
const groups = [...(action.payload.groups || [])];
|
|
||||||
|
|
||||||
const id = user.id;
|
|
||||||
const firstName = user.firstName || '';
|
|
||||||
const lastName = user.lastName || '';
|
|
||||||
const userName = `${firstName} ${lastName}`;
|
|
||||||
const initials = [firstName[0], lastName[0]].join('');
|
|
||||||
const email = user.email;
|
|
||||||
|
|
||||||
const capabilities = user.capabilities;
|
|
||||||
const isAdmin = capabilities ? capabilities.isAdmin : true;
|
|
||||||
|
|
||||||
newState.user = {
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
userName,
|
|
||||||
initials,
|
|
||||||
isAdmin,
|
|
||||||
id,
|
|
||||||
groups,
|
|
||||||
email
|
|
||||||
};
|
|
||||||
|
|
||||||
return newState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCurrentFolder(state: AppState, action: SetCurrentFolderAction) {
|
function updateCurrentFolder(state: AppState, action: SetCurrentFolderAction) {
|
||||||
|
@ -26,20 +26,19 @@ import { AppService } from './app.service';
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import {
|
import {
|
||||||
AuthenticationService,
|
AuthenticationService,
|
||||||
AppConfigService,
|
|
||||||
AlfrescoApiService,
|
AlfrescoApiService,
|
||||||
PageTitleService,
|
PageTitleService,
|
||||||
AlfrescoApiServiceMock,
|
AlfrescoApiServiceMock,
|
||||||
TranslationMock,
|
TranslationMock,
|
||||||
TranslationService,
|
TranslationService,
|
||||||
UserPreferencesService
|
UserPreferencesService,
|
||||||
|
NotificationService
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
|
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import {
|
import {
|
||||||
DiscoveryApiService,
|
DiscoveryApiService,
|
||||||
FileUploadErrorEvent,
|
FileUploadErrorEvent,
|
||||||
GroupService,
|
|
||||||
SearchQueryBuilderService,
|
SearchQueryBuilderService,
|
||||||
SharedLinksApiService,
|
SharedLinksApiService,
|
||||||
UploadService
|
UploadService
|
||||||
@ -53,25 +52,26 @@ import { MatDialogModule } from '@angular/material/dialog';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { ContentApiService } from './content-api.service';
|
import { ContentApiService } from './content-api.service';
|
||||||
import { SetRepositoryInfoAction, SetUserProfileAction, SnackbarErrorAction } from '@alfresco/aca-shared/store';
|
import { AppSettingsService, UserProfileService } from '@alfresco/aca-shared';
|
||||||
import { AppSettingsService } from '@alfresco/aca-shared';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
|
|
||||||
describe('AppService', () => {
|
describe('AppService', () => {
|
||||||
let service: AppService;
|
let service: AppService;
|
||||||
let auth: AuthenticationService;
|
let auth: AuthenticationService;
|
||||||
let appConfig: AppConfigService;
|
|
||||||
let searchQueryBuilderService: SearchQueryBuilderService;
|
let searchQueryBuilderService: SearchQueryBuilderService;
|
||||||
let uploadService: UploadService;
|
let uploadService: UploadService;
|
||||||
let store: Store;
|
let store: Store;
|
||||||
let sharedLinksApiService: SharedLinksApiService;
|
let sharedLinksApiService: SharedLinksApiService;
|
||||||
let contentApi: ContentApiService;
|
let contentApi: ContentApiService;
|
||||||
let groupService: GroupService;
|
|
||||||
let preferencesService: UserPreferencesService;
|
let preferencesService: UserPreferencesService;
|
||||||
let appSettingsService: AppSettingsService;
|
let appSettingsService: AppSettingsService;
|
||||||
|
let userProfileService: UserProfileService;
|
||||||
|
let notificationService: NotificationService;
|
||||||
|
let loadUserProfileSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, HttpClientModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), MatDialogModule],
|
imports: [CommonModule, HttpClientModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), MatDialogModule, MatSnackBarModule],
|
||||||
providers: [
|
providers: [
|
||||||
SearchQueryBuilderService,
|
SearchQueryBuilderService,
|
||||||
provideMockStore({}),
|
provideMockStore({}),
|
||||||
@ -118,31 +118,18 @@ describe('AppService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
appSettingsService = TestBed.inject(AppSettingsService);
|
appSettingsService = TestBed.inject(AppSettingsService);
|
||||||
appConfig = TestBed.inject(AppConfigService);
|
|
||||||
auth = TestBed.inject(AuthenticationService);
|
auth = TestBed.inject(AuthenticationService);
|
||||||
searchQueryBuilderService = TestBed.inject(SearchQueryBuilderService);
|
searchQueryBuilderService = TestBed.inject(SearchQueryBuilderService);
|
||||||
uploadService = TestBed.inject(UploadService);
|
uploadService = TestBed.inject(UploadService);
|
||||||
store = TestBed.inject(Store);
|
store = TestBed.inject(Store);
|
||||||
sharedLinksApiService = TestBed.inject(SharedLinksApiService);
|
sharedLinksApiService = TestBed.inject(SharedLinksApiService);
|
||||||
contentApi = TestBed.inject(ContentApiService);
|
contentApi = TestBed.inject(ContentApiService);
|
||||||
groupService = TestBed.inject(GroupService);
|
spyOn(contentApi, 'getRepositoryInformation').and.returnValue(of({} as any));
|
||||||
service = TestBed.inject(AppService);
|
service = TestBed.inject(AppService);
|
||||||
preferencesService = TestBed.inject(UserPreferencesService);
|
preferencesService = TestBed.inject(UserPreferencesService);
|
||||||
});
|
userProfileService = TestBed.inject(UserProfileService);
|
||||||
|
loadUserProfileSpy = spyOn(userProfileService, 'loadUserProfile').and.returnValue(Promise.resolve({} as any));
|
||||||
it('should be ready if [withCredentials] mode is used', (done) => {
|
notificationService = TestBed.inject(NotificationService);
|
||||||
appConfig.config = {
|
|
||||||
auth: {
|
|
||||||
withCredentials: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const instance = TestBed.inject(AppService);
|
|
||||||
expect(instance.withCredentials).toBeTruthy();
|
|
||||||
|
|
||||||
instance.ready$.subscribe(() => {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be ready after login', async () => {
|
it('should be ready after login', async () => {
|
||||||
@ -170,45 +157,46 @@ describe('AppService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should raise notification on share link error', () => {
|
it('should raise notification on share link error', () => {
|
||||||
|
const showError = spyOn(notificationService, 'showError').and.stub();
|
||||||
spyOn(store, 'select').and.returnValue(of(''));
|
spyOn(store, 'select').and.returnValue(of(''));
|
||||||
service.init();
|
service.init();
|
||||||
const dispatch = spyOn(store, 'dispatch');
|
|
||||||
|
|
||||||
sharedLinksApiService.error.next({ message: 'Error Message', statusCode: 1 });
|
sharedLinksApiService.error.next({ message: 'Error Message', statusCode: 1 });
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('Error Message'));
|
expect(showError).toHaveBeenCalledWith('Error Message');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should raise notification on upload error', async () => {
|
it('should raise notification on upload error', async () => {
|
||||||
spyOn(store, 'select').and.returnValue(of(''));
|
spyOn(store, 'select').and.returnValue(of(''));
|
||||||
service.init();
|
service.init();
|
||||||
const dispatch = spyOn(store, 'dispatch');
|
|
||||||
|
const showError = spyOn(notificationService, 'showError').and.stub();
|
||||||
|
|
||||||
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 403 }));
|
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 403 }));
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.403'));
|
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.403');
|
||||||
dispatch.calls.reset();
|
showError.calls.reset();
|
||||||
|
|
||||||
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 404 }));
|
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 404 }));
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.404'));
|
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.404');
|
||||||
dispatch.calls.reset();
|
showError.calls.reset();
|
||||||
|
|
||||||
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 409 }));
|
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 409 }));
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.CONFLICT'));
|
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.CONFLICT');
|
||||||
dispatch.calls.reset();
|
showError.calls.reset();
|
||||||
|
|
||||||
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 500 }));
|
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 500 }));
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.500'));
|
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.500');
|
||||||
dispatch.calls.reset();
|
showError.calls.reset();
|
||||||
|
|
||||||
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 504 }));
|
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 504 }));
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.504'));
|
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.504');
|
||||||
dispatch.calls.reset();
|
showError.calls.reset();
|
||||||
|
|
||||||
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 403 }));
|
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 403 }));
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.403'));
|
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.403');
|
||||||
dispatch.calls.reset();
|
showError.calls.reset();
|
||||||
|
|
||||||
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, {}));
|
uploadService.fileUploadError.next(new FileUploadErrorEvent(null, {}));
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.GENERIC'));
|
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.GENERIC');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load custom css', () => {
|
it('should load custom css', () => {
|
||||||
@ -225,34 +213,19 @@ describe('AppService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should load repository status on login', () => {
|
it('should load repository status on login', () => {
|
||||||
const repository: any = {};
|
|
||||||
spyOn(contentApi, 'getRepositoryInformation').and.returnValue(of({ entry: { repository } }));
|
|
||||||
spyOn(store, 'select').and.returnValue(of(''));
|
|
||||||
service.init();
|
service.init();
|
||||||
|
|
||||||
const dispatch = spyOn(store, 'dispatch');
|
|
||||||
auth.onLogin.next(true);
|
auth.onLogin.next(true);
|
||||||
|
expect(contentApi.getRepositoryInformation).toHaveBeenCalled();
|
||||||
expect(dispatch).toHaveBeenCalledWith(new SetRepositoryInfoAction(repository));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load user profile on login', async () => {
|
it('should load user profile on login', async () => {
|
||||||
const person: any = { id: 'person' };
|
const person: any = { id: 'person' };
|
||||||
|
|
||||||
const group: any = { entry: {} };
|
loadUserProfileSpy.and.returnValue(Promise.resolve(person));
|
||||||
const groups: any[] = [group];
|
|
||||||
|
|
||||||
spyOn(contentApi, 'getRepositoryInformation').and.returnValue(of({} as any));
|
|
||||||
spyOn(groupService, 'listAllGroupMembershipsForPerson').and.returnValue(Promise.resolve(groups));
|
|
||||||
spyOn(contentApi, 'getPerson').and.returnValue(of({ entry: person }));
|
|
||||||
|
|
||||||
spyOn(store, 'select').and.returnValue(of(''));
|
spyOn(store, 'select').and.returnValue(of(''));
|
||||||
service.init();
|
service.init();
|
||||||
|
|
||||||
const dispatch = spyOn(store, 'dispatch');
|
|
||||||
auth.onLogin.next(true);
|
auth.onLogin.next(true);
|
||||||
|
|
||||||
await expect(groupService.listAllGroupMembershipsForPerson).toHaveBeenCalled();
|
expect(loadUserProfileSpy).toHaveBeenCalled();
|
||||||
await expect(dispatch).toHaveBeenCalledWith(new SetUserProfileAction({ person, groups: [group.entry] }));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,39 +22,51 @@
|
|||||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable, OnDestroy } from '@angular/core';
|
import { inject, Injectable, OnDestroy } from '@angular/core';
|
||||||
import { AuthenticationService, AppConfigService, AlfrescoApiService, PageTitleService, UserPreferencesService } from '@alfresco/adf-core';
|
import {
|
||||||
|
AuthenticationService,
|
||||||
|
AppConfigService,
|
||||||
|
AlfrescoApiService,
|
||||||
|
PageTitleService,
|
||||||
|
UserPreferencesService,
|
||||||
|
NotificationService
|
||||||
|
} from '@alfresco/adf-core';
|
||||||
import { Observable, BehaviorSubject, Subject } from 'rxjs';
|
import { Observable, BehaviorSubject, Subject } from 'rxjs';
|
||||||
import { GroupService, SearchQueryBuilderService, SharedLinksApiService, UploadService, FileUploadErrorEvent } from '@alfresco/adf-content-services';
|
import { SearchQueryBuilderService, SharedLinksApiService, UploadService, FileUploadErrorEvent } from '@alfresco/adf-content-services';
|
||||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
import { ActivatedRoute, ActivationEnd, NavigationStart, Router } from '@angular/router';
|
import { ActivatedRoute, ActivationEnd, NavigationStart, Router } from '@angular/router';
|
||||||
import { filter, map, tap } from 'rxjs/operators';
|
import { filter, map } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
AppStore,
|
AppStore,
|
||||||
CloseModalDialogsAction,
|
CloseModalDialogsAction,
|
||||||
SetCurrentUrlAction,
|
SetCurrentUrlAction,
|
||||||
SetRepositoryInfoAction,
|
SetRepositoryInfoAction,
|
||||||
SetUserProfileAction,
|
SetUserProfileAction,
|
||||||
SnackbarErrorAction,
|
|
||||||
ResetSelectionAction
|
ResetSelectionAction
|
||||||
} from '@alfresco/aca-shared/store';
|
} from '@alfresco/aca-shared/store';
|
||||||
import { ContentApiService } from './content-api.service';
|
import { ContentApiService } from './content-api.service';
|
||||||
import { RouterExtensionService } from './router.extension.service';
|
import { RouterExtensionService } from './router.extension.service';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { DiscoveryEntry, GroupEntry, Group } from '@alfresco/js-api';
|
import { DiscoveryEntry } from '@alfresco/js-api';
|
||||||
import { AcaMobileAppSwitcherService } from './aca-mobile-app-switcher.service';
|
import { AcaMobileAppSwitcherService } from './aca-mobile-app-switcher.service';
|
||||||
import { ShellAppService } from '@alfresco/adf-core/shell';
|
import { ShellAppService } from '@alfresco/adf-core/shell';
|
||||||
import { AppSettingsService } from './app-settings.service';
|
import { AppSettingsService } from './app-settings.service';
|
||||||
|
import { UserProfileService } from './user-profile.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
// After moving shell to ADF to core, AppService will implement ShellAppService
|
// After moving shell to ADF to core, AppService will implement ShellAppService
|
||||||
export class AppService implements ShellAppService, OnDestroy {
|
export class AppService implements ShellAppService, OnDestroy {
|
||||||
|
private notificationService = inject(NotificationService);
|
||||||
private ready: BehaviorSubject<boolean>;
|
private ready: BehaviorSubject<boolean>;
|
||||||
|
|
||||||
ready$: Observable<boolean>;
|
ready$: Observable<boolean>;
|
||||||
pageHeading$: Observable<string>;
|
|
||||||
|
private pageHeading = new BehaviorSubject('');
|
||||||
|
/** @deprecated page title is updated automatically */
|
||||||
|
pageHeading$ = this.pageHeading.asObservable();
|
||||||
|
|
||||||
appNavNarMode$: Subject<'collapsed' | 'expanded'> = new BehaviorSubject('expanded');
|
appNavNarMode$: Subject<'collapsed' | 'expanded'> = new BehaviorSubject('expanded');
|
||||||
toggleAppNavBar$ = new Subject();
|
toggleAppNavBar$ = new Subject();
|
||||||
|
|
||||||
@ -84,11 +96,11 @@ export class AppService implements ShellAppService, OnDestroy {
|
|||||||
private routerExtensionService: RouterExtensionService,
|
private routerExtensionService: RouterExtensionService,
|
||||||
private contentApi: ContentApiService,
|
private contentApi: ContentApiService,
|
||||||
private sharedLinksApiService: SharedLinksApiService,
|
private sharedLinksApiService: SharedLinksApiService,
|
||||||
private groupService: GroupService,
|
|
||||||
private overlayContainer: OverlayContainer,
|
private overlayContainer: OverlayContainer,
|
||||||
searchQueryBuilderService: SearchQueryBuilderService,
|
searchQueryBuilderService: SearchQueryBuilderService,
|
||||||
private acaMobileAppSwitcherService: AcaMobileAppSwitcherService,
|
private acaMobileAppSwitcherService: AcaMobileAppSwitcherService,
|
||||||
private appSettingsService: AppSettingsService
|
private appSettingsService: AppSettingsService,
|
||||||
|
private userProfileService: UserProfileService
|
||||||
) {
|
) {
|
||||||
this.ready = new BehaviorSubject(this.authenticationService.isLoggedIn() || this.withCredentials);
|
this.ready = new BehaviorSubject(this.authenticationService.isLoggedIn() || this.withCredentials);
|
||||||
this.ready$ = this.ready.asObservable();
|
this.ready$ = this.ready.asObservable();
|
||||||
@ -104,11 +116,15 @@ export class AppService implements ShellAppService, OnDestroy {
|
|||||||
acaMobileAppSwitcherService.closeDialog();
|
acaMobileAppSwitcherService.closeDialog();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pageHeading$ = this.router.events.pipe(
|
this.router.events
|
||||||
|
.pipe(
|
||||||
filter((event) => event instanceof ActivationEnd && event.snapshot.children.length === 0),
|
filter((event) => event instanceof ActivationEnd && event.snapshot.children.length === 0),
|
||||||
map((event: ActivationEnd) => event.snapshot?.data?.title ?? ''),
|
map((event: ActivationEnd) => event.snapshot?.data?.title ?? '')
|
||||||
tap((title) => this.pageTitle.setTitle(title))
|
)
|
||||||
);
|
.subscribe((title) => {
|
||||||
|
this.pageHeading.next(title);
|
||||||
|
this.pageTitle.setTitle(title);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@ -153,7 +169,7 @@ export class AppService implements ShellAppService, OnDestroy {
|
|||||||
|
|
||||||
this.sharedLinksApiService.error.subscribe((err: { message: string }) => {
|
this.sharedLinksApiService.error.subscribe((err: { message: string }) => {
|
||||||
if (err?.message) {
|
if (err?.message) {
|
||||||
this.store.dispatch(new SnackbarErrorAction(err.message));
|
this.notificationService.showError(err.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -184,17 +200,8 @@ export class AppService implements ShellAppService, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadUserProfile() {
|
private async loadUserProfile() {
|
||||||
const groupsEntries: GroupEntry[] = await this.groupService.listAllGroupMembershipsForPerson('-me-', { maxItems: 250 });
|
const profile = await this.userProfileService.loadUserProfile();
|
||||||
|
this.store.dispatch(new SetUserProfileAction(profile));
|
||||||
const groups: Group[] = [];
|
|
||||||
|
|
||||||
if (groupsEntries) {
|
|
||||||
groups.push(...groupsEntries.map((obj) => obj.entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.contentApi.getPerson('-me-').subscribe((person) => {
|
|
||||||
this.store.dispatch(new SetUserProfileAction({ person: person.entry, groups }));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileUploadedError(error: FileUploadErrorEvent) {
|
onFileUploadedError(error: FileUploadErrorEvent) {
|
||||||
@ -220,7 +227,7 @@ export class AppService implements ShellAppService, OnDestroy {
|
|||||||
message = 'APP.MESSAGES.UPLOAD.ERROR.504';
|
message = 'APP.MESSAGES.UPLOAD.ERROR.504';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.store.dispatch(new SnackbarErrorAction(message));
|
this.notificationService.showError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadCustomCss(): void {
|
private loadCustomCss(): void {
|
||||||
|
81
projects/aca-shared/src/lib/services/user-profile.service.ts
Normal file
81
projects/aca-shared/src/lib/services/user-profile.service.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { ProfileState } from '@alfresco/adf-extensions';
|
||||||
|
import { GroupService } from '@alfresco/adf-content-services';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||||
|
import { PeopleApi } from '@alfresco/js-api';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class UserProfileService {
|
||||||
|
private api = inject(AlfrescoApiService);
|
||||||
|
private groupService = inject(GroupService);
|
||||||
|
|
||||||
|
private get peopleApi(): PeopleApi {
|
||||||
|
return new PeopleApi(this.api.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
private userProfile = new BehaviorSubject<ProfileState>(null);
|
||||||
|
userProfile$ = this.userProfile.asObservable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load user profile.
|
||||||
|
*/
|
||||||
|
async loadUserProfile(): Promise<ProfileState> {
|
||||||
|
const groupsEntries = await this.groupService.listAllGroupMembershipsForPerson('-me-', { maxItems: 250 });
|
||||||
|
|
||||||
|
const groups = [];
|
||||||
|
if (groupsEntries) {
|
||||||
|
groups.push(...groupsEntries.map((obj) => obj.entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entry: user } = await this.peopleApi.getPerson('-me-');
|
||||||
|
|
||||||
|
const id = user.id;
|
||||||
|
const firstName = user.firstName || '';
|
||||||
|
const lastName = user.lastName || '';
|
||||||
|
const userName = `${firstName} ${lastName}`;
|
||||||
|
const initials = [firstName[0], lastName[0]].join('');
|
||||||
|
const email = user.email;
|
||||||
|
|
||||||
|
const capabilities = user.capabilities;
|
||||||
|
const isAdmin = capabilities ? capabilities.isAdmin : true;
|
||||||
|
|
||||||
|
const profile: ProfileState = {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
userName,
|
||||||
|
initials,
|
||||||
|
isAdmin,
|
||||||
|
id,
|
||||||
|
groups,
|
||||||
|
email
|
||||||
|
};
|
||||||
|
|
||||||
|
this.userProfile.next(profile);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,7 @@ export * from './lib/services/router.extension.service';
|
|||||||
export * from './lib/services/app-hook.service';
|
export * from './lib/services/app-hook.service';
|
||||||
export * from './lib/services/aca-file-auto-download.service';
|
export * from './lib/services/aca-file-auto-download.service';
|
||||||
export * from './lib/services/app-settings.service';
|
export * from './lib/services/app-settings.service';
|
||||||
|
export * from './lib/services/user-profile.service';
|
||||||
|
|
||||||
export * from './lib/utils/node.utils';
|
export * from './lib/utils/node.utils';
|
||||||
export * from './lib/testing/lib-testing-module';
|
export * from './lib/testing/lib-testing-module';
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
import { Node, Person, Group, RepositoryInfo, VersionEntry } from '@alfresco/js-api';
|
import { Node, RepositoryInfo, VersionEntry } from '@alfresco/js-api';
|
||||||
import { AppActionTypes } from './app-action-types';
|
import { AppActionTypes } from './app-action-types';
|
||||||
|
import { ProfileState } from '@alfresco/adf-extensions';
|
||||||
|
|
||||||
export class SetCurrentFolderAction implements Action {
|
export class SetCurrentFolderAction implements Action {
|
||||||
readonly type = AppActionTypes.SetCurrentFolder;
|
readonly type = AppActionTypes.SetCurrentFolder;
|
||||||
@ -47,7 +48,7 @@ export class SetCurrentUrlAction implements Action {
|
|||||||
export class SetUserProfileAction implements Action {
|
export class SetUserProfileAction implements Action {
|
||||||
readonly type = AppActionTypes.SetUserProfile;
|
readonly type = AppActionTypes.SetUserProfile;
|
||||||
|
|
||||||
constructor(public payload: { person: Person; groups: Group[] }) {}
|
constructor(public payload: ProfileState) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ToggleInfoDrawerAction implements Action {
|
export class ToggleInfoDrawerAction implements Action {
|
||||||
|
@ -28,6 +28,7 @@ import { createSelector } from '@ngrx/store';
|
|||||||
const HXI_CONNECTOR = 'alfresco-hxinsight-connector-prediction-applier-extension';
|
const HXI_CONNECTOR = 'alfresco-hxinsight-connector-prediction-applier-extension';
|
||||||
export const selectApp = (state: AppStore) => state.app;
|
export const selectApp = (state: AppStore) => state.app;
|
||||||
|
|
||||||
|
/** @deprecated use `UserProfileService` instead */
|
||||||
export const getUserProfile = createSelector(selectApp, (state) => state.user);
|
export const getUserProfile = createSelector(selectApp, (state) => state.user);
|
||||||
export const getCurrentFolder = createSelector(selectApp, (state) => state.navigation.currentFolder);
|
export const getCurrentFolder = createSelector(selectApp, (state) => state.navigation.currentFolder);
|
||||||
export const getCurrentVersion = createSelector(selectApp, (state) => state.currentNodeVersion);
|
export const getCurrentVersion = createSelector(selectApp, (state) => state.currentNodeVersion);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user