diff --git a/projects/aca-shared/rules/src/app.rules.ts b/projects/aca-shared/rules/src/app.rules.ts index 2f766e4e2..dfbff769e 100644 --- a/projects/aca-shared/rules/src/app.rules.ts +++ b/projects/aca-shared/rules/src/app.rules.ts @@ -26,6 +26,7 @@ import { RuleContext } from '@alfresco/adf-extensions'; import * as navigation from './navigation.rules'; import * as repository from './repository.rules'; +import { isAdmin } from './user.rules'; export interface AcaRuleContext extends RuleContext { withCredentials: boolean; @@ -81,7 +82,10 @@ export function canShareFile(context: RuleContext): boolean { * JSON ref: `canToggleJoinLibrary` */ export function canToggleJoinLibrary(context: RuleContext): boolean { - return [hasLibrarySelected(context), !isPrivateLibrary(context), hasNoLibraryRole(context)].every(Boolean); + return ( + [hasLibrarySelected(context), !isPrivateLibrary(context), hasNoLibraryRole(context)].every(Boolean) || + [hasLibrarySelected(context), isPrivateLibrary(context), hasNoLibraryRole(context), isAdmin(context)].every(Boolean) + ); } /** diff --git a/projects/aca-shared/store/src/actions/library.actions.ts b/projects/aca-shared/store/src/actions/library.actions.ts index dcd03c509..c2de77ce8 100644 --- a/projects/aca-shared/store/src/actions/library.actions.ts +++ b/projects/aca-shared/store/src/actions/library.actions.ts @@ -31,7 +31,8 @@ export enum LibraryActionTypes { Create = 'CREATE_LIBRARY', Navigate = 'NAVIGATE_LIBRARY', Update = 'UPDATE_LIBRARY', - Leave = 'LEAVE_LIBRARY' + Leave = 'LEAVE_LIBRARY', + Reload = 'RELOAD_LIBRARY' } export class DeleteLibraryAction implements Action { @@ -61,3 +62,6 @@ export class LeaveLibraryAction implements Action { constructor(public payload?: string) {} } +export class ReloadLibraryAction implements Action { + readonly type = LibraryActionTypes.Reload; +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f9b261723..cb0398323 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -79,7 +79,7 @@ export class AppComponent implements OnInit, OnDestroy { ngOnInit() { this.alfrescoApiService.getInstance().on('error', (error: { status: number; response: any }) => { - if (error.status === 401 && !this.alfrescoApiService.isExcludedErrorListener(error?.response?.req?.url)) { + if (error.status === 401) { if (!this.authenticationService.isLoggedIn()) { this.store.dispatch(new CloseModalDialogsAction()); diff --git a/src/app/components/toolbar/toggle-join-library/toggle-join-library-button.component.ts b/src/app/components/toolbar/toggle-join-library/toggle-join-library-button.component.ts index 256cb66f8..7a75db584 100644 --- a/src/app/components/toolbar/toggle-join-library/toggle-join-library-button.component.ts +++ b/src/app/components/toolbar/toggle-join-library/toggle-join-library-button.component.ts @@ -23,8 +23,16 @@ * along with Alfresco. If not, see . */ -import { AppStore, SetSelectedNodesAction, SnackbarErrorAction, SnackbarInfoAction, getAppSelection } from '@alfresco/aca-shared/store'; -import { SelectionState } from '@alfresco/adf-extensions'; +import { + AppStore, + SetSelectedNodesAction, + SnackbarErrorAction, + SnackbarInfoAction, + getAppSelection, + getUserProfile, + ReloadLibraryAction +} from '@alfresco/aca-shared/store'; +import { ProfileState, SelectionState } from '@alfresco/adf-extensions'; import { Component, ViewEncapsulation } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -41,6 +49,7 @@ import { ContentManagementService } from '../../../services/content-management.s (toggle)="onToggleEvent($event)" (error)="onErrorEvent($event)" [acaLibraryMembership]="(selection$ | async).library" + [isAdmin]="(profile$ | async).isAdmin" [attr.title]="(membership.isJoinRequested | async) ? ('APP.ACTIONS.CANCEL_JOIN' | translate) : ('APP.ACTIONS.JOIN' | translate)" > cancel @@ -52,9 +61,11 @@ import { ContentManagementService } from '../../../services/content-management.s }) export class ToggleJoinLibraryButtonComponent { selection$: Observable; + profile$: Observable; constructor(private store: Store, private content: ContentManagementService) { this.selection$ = this.store.select(getAppSelection); + this.profile$ = this.store.select(getUserProfile); } onToggleEvent(event: LibraryMembershipToggleEvent) { @@ -62,6 +73,7 @@ export class ToggleJoinLibraryButtonComponent { if (event.shouldReload) { this.content.libraryJoined.next(); + this.store.dispatch(new ReloadLibraryAction()); } else { if (event.updatedEntry) { this.store.dispatch(new SetSelectedNodesAction([{ entry: event.updatedEntry, isLibrary: true } as any])); diff --git a/src/app/components/toolbar/toggle-join-library/toggle-join-library.component.spec.ts b/src/app/components/toolbar/toggle-join-library/toggle-join-library.component.spec.ts index 7e904ac2c..eaeec0b4e 100644 --- a/src/app/components/toolbar/toggle-join-library/toggle-join-library.component.spec.ts +++ b/src/app/components/toolbar/toggle-join-library/toggle-join-library.component.spec.ts @@ -29,7 +29,7 @@ import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-core'; import { LibraryMembershipDirective } from '../../../directives/library-membership.directive'; import { Store } from '@ngrx/store'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store'; +import { ReloadLibraryAction, SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store'; import { AppTestingModule } from '../../../testing/app-testing.module'; import { ContentManagementService } from '../../../services/content-management.service'; import { ToggleJoinLibraryButtonComponent } from './toggle-join-library-button.component'; @@ -98,6 +98,13 @@ describe('ToggleJoinLibraryComponent', () => { expect(storeMock.dispatch).toHaveBeenCalledWith(new SnackbarInfoAction(event.i18nKey)); }); + it('should dispatch `ReloadLibraryAction` action on onToggleEvent', () => { + const event = { shouldReload: true, i18nKey: 'SOME_i18nKey' }; + component.onToggleEvent(event); + + expect(storeMock.dispatch).toHaveBeenCalledWith(new ReloadLibraryAction()); + }); + it('should call libraryJoined.next on contentManagementService onToggleEvent', (done) => { spyOn(contentManagementService.libraryJoined, 'next').and.callThrough(); diff --git a/src/app/directives/library-membership.directive.spec.ts b/src/app/directives/library-membership.directive.spec.ts index 2b7b386f8..1326d7162 100644 --- a/src/app/directives/library-membership.directive.spec.ts +++ b/src/app/directives/library-membership.directive.spec.ts @@ -24,18 +24,19 @@ */ import { fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { AlfrescoApiService, AlfrescoApiServiceMock, AppConfigService, CoreModule, StorageService } from '@alfresco/adf-core'; +import { AlfrescoApiService, AlfrescoApiServiceMock, AppConfigService, CoreModule, SitesService, StorageService } from '@alfresco/adf-core'; import { AppTestingModule } from '../testing/app-testing.module'; import { DirectivesModule } from './directives.module'; import { LibraryMembershipDirective } from './library-membership.directive'; import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; -import { throwError } from 'rxjs'; +import { of, throwError } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; describe('LibraryMembershipDirective', () => { let alfrescoApiService: AlfrescoApiService; let directive: LibraryMembershipDirective; let peopleApi; + let sitesService; let addMembershipSpy; let getMembershipSpy; let deleteMembershipSpy; @@ -58,8 +59,9 @@ describe('LibraryMembershipDirective', () => { schemas: [NO_ERRORS_SCHEMA] }); alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()); + sitesService = new SitesService(alfrescoApiService); peopleApi = alfrescoApiService.getInstance().core.peopleApi; - directive = new LibraryMembershipDirective(alfrescoApiService); + directive = new LibraryMembershipDirective(alfrescoApiService, sitesService); }); describe('markMembershipRequest', () => { @@ -141,6 +143,19 @@ describe('LibraryMembershipDirective', () => { expect(deleteMembershipSpy).not.toHaveBeenCalled(); })); + it('should call API to add user to library if admin user', fakeAsync(() => { + const createSiteMembershipSpy = spyOn(sitesService, 'createSiteMembership').and.returnValue(of({})); + const selection = { entry: { id: 'no-membership-requested' } }; + const selectionChange = new SimpleChange(null, selection, true); + directive.isAdmin = true; + directive.ngOnChanges({ selection: selectionChange }); + tick(); + directive.toggleMembershipRequest(); + tick(); + expect(createSiteMembershipSpy).toHaveBeenCalled(); + expect(addMembershipSpy).not.toHaveBeenCalled(); + })); + it('should emit error when the request to join a library fails', fakeAsync(() => { spyOn(directive.error, 'emit'); addMembershipSpy.and.returnValue(throwError('err')); diff --git a/src/app/directives/library-membership.directive.ts b/src/app/directives/library-membership.directive.ts index c6a393f02..c97fb96e7 100644 --- a/src/app/directives/library-membership.directive.ts +++ b/src/app/directives/library-membership.directive.ts @@ -24,8 +24,8 @@ */ import { Directive, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; -import { SiteEntry, SiteMembershipRequestBody } from '@alfresco/js-api'; -import { AlfrescoApiService } from '@alfresco/adf-core'; +import { SiteEntry, SiteMemberEntry, SiteMembershipRequestBody } from '@alfresco/js-api'; +import { AlfrescoApiService, SitesService } from '@alfresco/adf-core'; import { BehaviorSubject, from } from 'rxjs'; export interface LibraryMembershipToggleEvent { @@ -52,6 +52,10 @@ export class LibraryMembershipDirective implements OnChanges { @Input('acaLibraryMembership') selection: SiteEntry = null; + /** Site for which to toggle the membership request. */ + @Input() + isAdmin = false; + @Output() toggle = new EventEmitter(); @@ -64,7 +68,7 @@ export class LibraryMembershipDirective implements OnChanges { this.toggleMembershipRequest(); } - constructor(private alfrescoApiService: AlfrescoApiService) {} + constructor(private alfrescoApiService: AlfrescoApiService, private sitesService: SitesService) {} ngOnChanges(changes: SimpleChanges) { if (!changes.selection.currentValue || !changes.selection.currentValue.entry) { @@ -103,7 +107,7 @@ export class LibraryMembershipDirective implements OnChanges { ); } - if (!this.targetSite.joinRequested) { + if (!this.targetSite.joinRequested && !this.isAdmin) { this.joinLibraryRequest().subscribe( (createdMembership) => { this.targetSite.joinRequested = true; @@ -145,6 +149,39 @@ export class LibraryMembershipDirective implements OnChanges { } ); } + + if (this.isAdmin) { + this.joinLibrary().subscribe( + (createdMembership: SiteMemberEntry) => { + if (createdMembership.entry && createdMembership.entry.role) { + const info = { + shouldReload: true, + i18nKey: 'APP.MESSAGES.INFO.JOINED' + }; + this.toggle.emit(info); + } + }, + (error) => { + const errWithMessage = { + error, + i18nKey: 'APP.MESSAGES.ERRORS.JOIN_REQUEST_FAILED' + }; + + const senderEmailCheck = 'Failed to resolve sender mail address'; + const receiverEmailCheck = 'All recipients for the mail action were invalid'; + + if (error.message) { + if (error.message.includes(senderEmailCheck)) { + errWithMessage.i18nKey = 'APP.MESSAGES.ERRORS.INVALID_SENDER_EMAIL'; + } else if (error.message.includes(receiverEmailCheck)) { + errWithMessage.i18nKey = 'APP.MESSAGES.ERRORS.INVALID_RECEIVER_EMAIL'; + } + } + + this.error.emit(errWithMessage); + } + ); + } } markMembershipRequest() { @@ -174,6 +211,13 @@ export class LibraryMembershipDirective implements OnChanges { return from(this.alfrescoApiService.peopleApi.addSiteMembershipRequest('-me-', memberBody)); } + private joinLibrary() { + return this.sitesService.createSiteMembership(this.targetSite.id, { + role: 'SiteConsumer', + id: '-me-' + }); + } + private cancelJoinRequest() { return from(this.alfrescoApiService.peopleApi.removeSiteMembershipRequest('-me-', this.targetSite.id)); } diff --git a/src/app/store/effects/library.effects.ts b/src/app/store/effects/library.effects.ts index abb833f0b..eee1dd424 100644 --- a/src/app/store/effects/library.effects.ts +++ b/src/app/store/effects/library.effects.ts @@ -33,7 +33,8 @@ import { NavigateRouteAction, SnackbarErrorAction, UpdateLibraryAction, - getAppSelection + getAppSelection, + ReloadLibraryAction } from '@alfresco/aca-shared/store'; import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; @@ -86,6 +87,7 @@ export class LibraryEffects { } }); } + this.store.dispatch(new ReloadLibraryAction()); }) );