Skip to content

Commit

Permalink
[ACS-8433] ACA: User Profile Service (#3957)
Browse files Browse the repository at this point in the history
  • Loading branch information
DenysVuika authored Jul 22, 2024
1 parent f23f5ed commit b5568d4
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 153 deletions.
9 changes: 1 addition & 8 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,13 @@ jobs:

unit-tests:
needs: [lint, build]
name: "Unit tests: ${{ matrix.unit-tests.name }}"
name: "Unit tests"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
unit-tests:
- name: "aca-content"
- name: "aca-shared"
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: node
uses: actions/setup-node@v3
with:
Expand Down
1 change: 0 additions & 1 deletion app/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
<h1 class="aca-sr-only" title="{{pageHeading | async | translate}}">{{ pageHeading | async | translate }}</h1>
<router-outlet></router-outlet>
4 changes: 1 addition & 3 deletions app/src/app/app.components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/

import { Component, ViewEncapsulation } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Subject } from 'rxjs';
import { AppService } from '@alfresco/aca-shared';

@Component({
Expand All @@ -34,10 +34,8 @@ import { AppService } from '@alfresco/aca-shared';
})
export class AppComponent {
onDestroy$: Subject<boolean> = new Subject<boolean>();
pageHeading: Observable<string>;

constructor(private appService: AppService) {
this.pageHeading = this.appService.pageHeading$;
this.appService.init();
}
}
2 changes: 1 addition & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ module.exports = () => {
check: {
global: {
statements: 75,
branches: 67,
branches: 65,
functions: 71,
lines: 74
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { MatMenuModule } from '@angular/material/menu';
import { TranslateModule } from '@ngx-translate/core';
import { AppStore, getUserProfile } from '@alfresco/aca-shared/store';
import { Store } from '@ngrx/store';
import { UserProfileService } from '@alfresco/aca-shared';

@Component({
standalone: true,
Expand All @@ -39,6 +38,7 @@ import { Store } from '@ngrx/store';
encapsulation: ViewEncapsulation.None
})
export class UserInfoComponent {
private store = inject<Store<AppStore>>(Store<AppStore>);
user$ = this.store.select(getUserProfile);
private userProfileService = inject(UserProfileService);

user$ = this.userProfileService.userProfile$;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { TranslateModule } from '@ngx-translate/core';
import { MatMenuModule } from '@angular/material/menu';
import { ToolbarMenuItemComponent } from '@alfresco/aca-shared';
import { AppStore, getUserProfile } from '@alfresco/aca-shared/store';
import { Store } from '@ngrx/store';
import { ToolbarMenuItemComponent, UserProfileService } from '@alfresco/aca-shared';

@Component({
standalone: true,
Expand All @@ -42,8 +40,9 @@ import { Store } from '@ngrx/store';
host: { class: 'aca-user-menu' }
})
export class UserMenuComponent implements OnInit {
private store = inject<Store<AppStore>>(Store<AppStore>);
user$ = this.store.select(getUserProfile);
private userProfileService = inject(UserProfileService);

user$ = this.userProfileService.userProfile$;

@Input()
actionRef: ContentActionRef;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,10 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/

import {
AppStore,
SetSelectedNodesAction,
SnackbarErrorAction,
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 { AppStore, SetSelectedNodesAction, SnackbarErrorAction, SnackbarInfoAction, getAppSelection } from '@alfresco/aca-shared/store';
import { AppHookService, UserProfileService } from '@alfresco/aca-shared';
import { SelectionState } from '@alfresco/adf-extensions';
import { Component, inject, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { LibraryMembershipDirective, LibraryMembershipErrorEvent, LibraryMembershipToggleEvent } from '@alfresco/adf-content-services';
Expand Down Expand Up @@ -64,12 +57,13 @@ import { MatIconModule } from '@angular/material/icon';
host: { class: 'app-toggle-join-library' }
})
export class ToggleJoinLibraryButtonComponent {
private userProfileService = inject(UserProfileService);

selection$: Observable<SelectionState>;
profile$: Observable<ProfileState>;
profile$ = this.userProfileService.userProfile$;

constructor(private store: Store<AppStore>, private appHookService: AppHookService) {
this.selection$ = this.store.select(getAppSelection);
this.profile$ = this.store.select(getUserProfile);
}

onToggleEvent(event: LibraryMembershipToggleEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@
* 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 { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ContextActionsDirective, PageComponent, PageLayoutComponent, PaginationDirective, ToolbarComponent } from '@alfresco/aca-shared';
import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
import {
ContextActionsDirective,
PageComponent,
PageLayoutComponent,
PaginationDirective,
ToolbarComponent,
UserProfileService
} from '@alfresco/aca-shared';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { DocumentListModule } from '@alfresco/adf-content-services';
Expand All @@ -52,7 +58,9 @@ import { DocumentListDirective } from '../../directives/document-list.directive'
encapsulation: ViewEncapsulation.None
})
export class TrashcanComponent extends PageComponent implements OnInit {
user$ = this.store.select(getUserProfile);
private userProfileService = inject(UserProfileService);

user$ = this.userProfileService.userProfile$;
columns: DocumentListPresetRef[] = [];

ngOnInit() {
Expand Down
27 changes: 1 addition & 26 deletions projects/aca-content/src/lib/store/reducers/app.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,32 +92,7 @@ export function appReducer(state: AppState = INITIAL_APP_STATE, action: Action):
}

function updateUser(state: AppState, action: SetUserProfileAction): AppState {
const newState = { ...state };
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;
return { ...state, user: { ...action.payload } };
}

function updateCurrentFolder(state: AppState, action: SetCurrentFolderAction) {
Expand Down
91 changes: 32 additions & 59 deletions projects/aca-shared/src/lib/services/app.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,19 @@ import { AppService } from './app.service';
import { TestBed } from '@angular/core/testing';
import {
AuthenticationService,
AppConfigService,
AlfrescoApiService,
PageTitleService,
AlfrescoApiServiceMock,
TranslationMock,
TranslationService,
UserPreferencesService
UserPreferencesService,
NotificationService
} from '@alfresco/adf-core';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { HttpClientModule } from '@angular/common/http';
import {
DiscoveryApiService,
FileUploadErrorEvent,
GroupService,
SearchQueryBuilderService,
SharedLinksApiService,
UploadService
Expand All @@ -53,25 +52,26 @@ import { MatDialogModule } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { Store } from '@ngrx/store';
import { ContentApiService } from './content-api.service';
import { SetRepositoryInfoAction, SetUserProfileAction, SnackbarErrorAction } from '@alfresco/aca-shared/store';
import { AppSettingsService } from '@alfresco/aca-shared';
import { AppSettingsService, UserProfileService } from '@alfresco/aca-shared';
import { MatSnackBarModule } from '@angular/material/snack-bar';

describe('AppService', () => {
let service: AppService;
let auth: AuthenticationService;
let appConfig: AppConfigService;
let searchQueryBuilderService: SearchQueryBuilderService;
let uploadService: UploadService;
let store: Store;
let sharedLinksApiService: SharedLinksApiService;
let contentApi: ContentApiService;
let groupService: GroupService;
let preferencesService: UserPreferencesService;
let appSettingsService: AppSettingsService;
let userProfileService: UserProfileService;
let notificationService: NotificationService;
let loadUserProfileSpy: jasmine.Spy;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [CommonModule, HttpClientModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), MatDialogModule],
imports: [CommonModule, HttpClientModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), MatDialogModule, MatSnackBarModule],
providers: [
SearchQueryBuilderService,
provideMockStore({}),
Expand Down Expand Up @@ -118,31 +118,18 @@ describe('AppService', () => {
});

appSettingsService = TestBed.inject(AppSettingsService);
appConfig = TestBed.inject(AppConfigService);
auth = TestBed.inject(AuthenticationService);
searchQueryBuilderService = TestBed.inject(SearchQueryBuilderService);
uploadService = TestBed.inject(UploadService);
store = TestBed.inject(Store);
sharedLinksApiService = TestBed.inject(SharedLinksApiService);
contentApi = TestBed.inject(ContentApiService);
groupService = TestBed.inject(GroupService);
spyOn(contentApi, 'getRepositoryInformation').and.returnValue(of({} as any));
service = TestBed.inject(AppService);
preferencesService = TestBed.inject(UserPreferencesService);
});

it('should be ready if [withCredentials] mode is used', (done) => {
appConfig.config = {
auth: {
withCredentials: true
}
};

const instance = TestBed.inject(AppService);
expect(instance.withCredentials).toBeTruthy();

instance.ready$.subscribe(() => {
done();
});
userProfileService = TestBed.inject(UserProfileService);
loadUserProfileSpy = spyOn(userProfileService, 'loadUserProfile').and.returnValue(Promise.resolve({} as any));
notificationService = TestBed.inject(NotificationService);
});

it('should be ready after login', async () => {
Expand Down Expand Up @@ -170,45 +157,46 @@ describe('AppService', () => {
});

it('should raise notification on share link error', () => {
const showError = spyOn(notificationService, 'showError').and.stub();
spyOn(store, 'select').and.returnValue(of(''));
service.init();
const dispatch = spyOn(store, 'dispatch');

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 () => {
spyOn(store, 'select').and.returnValue(of(''));
service.init();
const dispatch = spyOn(store, 'dispatch');

const showError = spyOn(notificationService, 'showError').and.stub();

uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 403 }));
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.403'));
dispatch.calls.reset();
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.403');
showError.calls.reset();

uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 404 }));
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.404'));
dispatch.calls.reset();
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.404');
showError.calls.reset();

uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 409 }));
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.CONFLICT'));
dispatch.calls.reset();
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.CONFLICT');
showError.calls.reset();

uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 500 }));
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.500'));
dispatch.calls.reset();
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.500');
showError.calls.reset();

uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 504 }));
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.504'));
dispatch.calls.reset();
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.504');
showError.calls.reset();

uploadService.fileUploadError.next(new FileUploadErrorEvent(null, { status: 403 }));
expect(dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.MESSAGES.UPLOAD.ERROR.403'));
dispatch.calls.reset();
expect(showError).toHaveBeenCalledWith('APP.MESSAGES.UPLOAD.ERROR.403');
showError.calls.reset();

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', () => {
Expand All @@ -225,34 +213,19 @@ describe('AppService', () => {
});

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

const dispatch = spyOn(store, 'dispatch');
auth.onLogin.next(true);

expect(dispatch).toHaveBeenCalledWith(new SetRepositoryInfoAction(repository));
expect(contentApi.getRepositoryInformation).toHaveBeenCalled();
});

it('should load user profile on login', async () => {
const person: any = { id: 'person' };

const group: any = { entry: {} };
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 }));

loadUserProfileSpy.and.returnValue(Promise.resolve(person));
spyOn(store, 'select').and.returnValue(of(''));
service.init();

const dispatch = spyOn(store, 'dispatch');
auth.onLogin.next(true);

await expect(groupService.listAllGroupMembershipsForPerson).toHaveBeenCalled();
await expect(dispatch).toHaveBeenCalledWith(new SetUserProfileAction({ person, groups: [group.entry] }));
expect(loadUserProfileSpy).toHaveBeenCalled();
});
});
Loading

0 comments on commit b5568d4

Please sign in to comment.