[ACS-8433] ACA: User Profile Service (#3957)

This commit is contained in:
Denys Vuika 2024-07-22 10:07:43 -04:00 committed by GitHub
parent f23f5edd08
commit b5568d43fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 183 additions and 153 deletions

View File

@ -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:

View File

@ -1,2 +1 @@
<h1 class="aca-sr-only" title="{{pageHeading | async | translate}}">{{ pageHeading | async | translate }}</h1>
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -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();
} }
} }

View File

@ -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
} }

View File

@ -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$;
} }

View File

@ -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;

View File

@ -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) {

View File

@ -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() {

View File

@ -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) {

View File

@ -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] }));
}); });
}); });

View File

@ -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
filter((event) => event instanceof ActivationEnd && event.snapshot.children.length === 0), .pipe(
map((event: ActivationEnd) => event.snapshot?.data?.title ?? ''), filter((event) => event instanceof ActivationEnd && event.snapshot.children.length === 0),
tap((title) => this.pageTitle.setTitle(title)) map((event: ActivationEnd) => event.snapshot?.data?.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 {

View 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;
}
}

View File

@ -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';

View File

@ -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 {

View File

@ -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);