Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 9 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -41,8 +39,9 @@ import { Store } from '@ngrx/store';
encapsulation: ViewEncapsulation.None
})
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
Loading