[ACA-3729] Join library action for Admin Users (#1801)

This commit is contained in:
davidcanonieto
2020-11-23 15:07:12 +00:00
committed by GitHub
parent 8186ee16ac
commit b504a224ea
8 changed files with 102 additions and 14 deletions

View File

@@ -26,6 +26,7 @@
import { RuleContext } from '@alfresco/adf-extensions'; import { RuleContext } from '@alfresco/adf-extensions';
import * as navigation from './navigation.rules'; import * as navigation from './navigation.rules';
import * as repository from './repository.rules'; import * as repository from './repository.rules';
import { isAdmin } from './user.rules';
export interface AcaRuleContext extends RuleContext { export interface AcaRuleContext extends RuleContext {
withCredentials: boolean; withCredentials: boolean;
@@ -81,7 +82,10 @@ export function canShareFile(context: RuleContext): boolean {
* JSON ref: `canToggleJoinLibrary` * JSON ref: `canToggleJoinLibrary`
*/ */
export function canToggleJoinLibrary(context: RuleContext): boolean { 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)
);
} }
/** /**

View File

@@ -31,7 +31,8 @@ export enum LibraryActionTypes {
Create = 'CREATE_LIBRARY', Create = 'CREATE_LIBRARY',
Navigate = 'NAVIGATE_LIBRARY', Navigate = 'NAVIGATE_LIBRARY',
Update = 'UPDATE_LIBRARY', Update = 'UPDATE_LIBRARY',
Leave = 'LEAVE_LIBRARY' Leave = 'LEAVE_LIBRARY',
Reload = 'RELOAD_LIBRARY'
} }
export class DeleteLibraryAction implements Action { export class DeleteLibraryAction implements Action {
@@ -61,3 +62,6 @@ export class LeaveLibraryAction implements Action {
constructor(public payload?: string) {} constructor(public payload?: string) {}
} }
export class ReloadLibraryAction implements Action {
readonly type = LibraryActionTypes.Reload;
}

View File

@@ -79,7 +79,7 @@ export class AppComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.alfrescoApiService.getInstance().on('error', (error: { status: number; response: any }) => { 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()) { if (!this.authenticationService.isLoggedIn()) {
this.store.dispatch(new CloseModalDialogsAction()); this.store.dispatch(new CloseModalDialogsAction());

View File

@@ -23,8 +23,16 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { AppStore, SetSelectedNodesAction, SnackbarErrorAction, SnackbarInfoAction, getAppSelection } from '@alfresco/aca-shared/store'; import {
import { SelectionState } from '@alfresco/adf-extensions'; 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 { Component, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -41,6 +49,7 @@ import { ContentManagementService } from '../../../services/content-management.s
(toggle)="onToggleEvent($event)" (toggle)="onToggleEvent($event)"
(error)="onErrorEvent($event)" (error)="onErrorEvent($event)"
[acaLibraryMembership]="(selection$ | async).library" [acaLibraryMembership]="(selection$ | async).library"
[isAdmin]="(profile$ | async).isAdmin"
[attr.title]="(membership.isJoinRequested | async) ? ('APP.ACTIONS.CANCEL_JOIN' | translate) : ('APP.ACTIONS.JOIN' | translate)" [attr.title]="(membership.isJoinRequested | async) ? ('APP.ACTIONS.CANCEL_JOIN' | translate) : ('APP.ACTIONS.JOIN' | translate)"
> >
<mat-icon *ngIf="membership.isJoinRequested | async">cancel</mat-icon> <mat-icon *ngIf="membership.isJoinRequested | async">cancel</mat-icon>
@@ -52,9 +61,11 @@ import { ContentManagementService } from '../../../services/content-management.s
}) })
export class ToggleJoinLibraryButtonComponent { export class ToggleJoinLibraryButtonComponent {
selection$: Observable<SelectionState>; selection$: Observable<SelectionState>;
profile$: Observable<ProfileState>;
constructor(private store: Store<AppStore>, private content: ContentManagementService) { constructor(private store: Store<AppStore>, private content: ContentManagementService) {
this.selection$ = this.store.select(getAppSelection); this.selection$ = this.store.select(getAppSelection);
this.profile$ = this.store.select(getUserProfile);
} }
onToggleEvent(event: LibraryMembershipToggleEvent) { onToggleEvent(event: LibraryMembershipToggleEvent) {
@@ -62,6 +73,7 @@ export class ToggleJoinLibraryButtonComponent {
if (event.shouldReload) { if (event.shouldReload) {
this.content.libraryJoined.next(); this.content.libraryJoined.next();
this.store.dispatch(new ReloadLibraryAction());
} else { } else {
if (event.updatedEntry) { if (event.updatedEntry) {
this.store.dispatch(new SetSelectedNodesAction([{ entry: event.updatedEntry, isLibrary: true } as any])); this.store.dispatch(new SetSelectedNodesAction([{ entry: event.updatedEntry, isLibrary: true } as any]));

View File

@@ -29,7 +29,7 @@ import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-core';
import { LibraryMembershipDirective } from '../../../directives/library-membership.directive'; import { LibraryMembershipDirective } from '../../../directives/library-membership.directive';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { NO_ERRORS_SCHEMA } from '@angular/core'; 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 { AppTestingModule } from '../../../testing/app-testing.module';
import { ContentManagementService } from '../../../services/content-management.service'; import { ContentManagementService } from '../../../services/content-management.service';
import { ToggleJoinLibraryButtonComponent } from './toggle-join-library-button.component'; import { ToggleJoinLibraryButtonComponent } from './toggle-join-library-button.component';
@@ -98,6 +98,13 @@ describe('ToggleJoinLibraryComponent', () => {
expect(storeMock.dispatch).toHaveBeenCalledWith(new SnackbarInfoAction(event.i18nKey)); 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) => { it('should call libraryJoined.next on contentManagementService onToggleEvent', (done) => {
spyOn(contentManagementService.libraryJoined, 'next').and.callThrough(); spyOn(contentManagementService.libraryJoined, 'next').and.callThrough();

View File

@@ -24,18 +24,19 @@
*/ */
import { fakeAsync, TestBed, tick } from '@angular/core/testing'; 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 { AppTestingModule } from '../testing/app-testing.module';
import { DirectivesModule } from './directives.module'; import { DirectivesModule } from './directives.module';
import { LibraryMembershipDirective } from './library-membership.directive'; import { LibraryMembershipDirective } from './library-membership.directive';
import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core';
import { throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
describe('LibraryMembershipDirective', () => { describe('LibraryMembershipDirective', () => {
let alfrescoApiService: AlfrescoApiService; let alfrescoApiService: AlfrescoApiService;
let directive: LibraryMembershipDirective; let directive: LibraryMembershipDirective;
let peopleApi; let peopleApi;
let sitesService;
let addMembershipSpy; let addMembershipSpy;
let getMembershipSpy; let getMembershipSpy;
let deleteMembershipSpy; let deleteMembershipSpy;
@@ -58,8 +59,9 @@ describe('LibraryMembershipDirective', () => {
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}); });
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()); alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
sitesService = new SitesService(alfrescoApiService);
peopleApi = alfrescoApiService.getInstance().core.peopleApi; peopleApi = alfrescoApiService.getInstance().core.peopleApi;
directive = new LibraryMembershipDirective(alfrescoApiService); directive = new LibraryMembershipDirective(alfrescoApiService, sitesService);
}); });
describe('markMembershipRequest', () => { describe('markMembershipRequest', () => {
@@ -141,6 +143,19 @@ describe('LibraryMembershipDirective', () => {
expect(deleteMembershipSpy).not.toHaveBeenCalled(); 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(() => { it('should emit error when the request to join a library fails', fakeAsync(() => {
spyOn(directive.error, 'emit'); spyOn(directive.error, 'emit');
addMembershipSpy.and.returnValue(throwError('err')); addMembershipSpy.and.returnValue(throwError('err'));

View File

@@ -24,8 +24,8 @@
*/ */
import { Directive, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { Directive, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { SiteEntry, SiteMembershipRequestBody } from '@alfresco/js-api'; import { SiteEntry, SiteMemberEntry, SiteMembershipRequestBody } from '@alfresco/js-api';
import { AlfrescoApiService } from '@alfresco/adf-core'; import { AlfrescoApiService, SitesService } from '@alfresco/adf-core';
import { BehaviorSubject, from } from 'rxjs'; import { BehaviorSubject, from } from 'rxjs';
export interface LibraryMembershipToggleEvent { export interface LibraryMembershipToggleEvent {
@@ -52,6 +52,10 @@ export class LibraryMembershipDirective implements OnChanges {
@Input('acaLibraryMembership') @Input('acaLibraryMembership')
selection: SiteEntry = null; selection: SiteEntry = null;
/** Site for which to toggle the membership request. */
@Input()
isAdmin = false;
@Output() @Output()
toggle = new EventEmitter<LibraryMembershipToggleEvent>(); toggle = new EventEmitter<LibraryMembershipToggleEvent>();
@@ -64,7 +68,7 @@ export class LibraryMembershipDirective implements OnChanges {
this.toggleMembershipRequest(); this.toggleMembershipRequest();
} }
constructor(private alfrescoApiService: AlfrescoApiService) {} constructor(private alfrescoApiService: AlfrescoApiService, private sitesService: SitesService) {}
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (!changes.selection.currentValue || !changes.selection.currentValue.entry) { 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( this.joinLibraryRequest().subscribe(
(createdMembership) => { (createdMembership) => {
this.targetSite.joinRequested = true; 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() { markMembershipRequest() {
@@ -174,6 +211,13 @@ export class LibraryMembershipDirective implements OnChanges {
return from(this.alfrescoApiService.peopleApi.addSiteMembershipRequest('-me-', memberBody)); return from(this.alfrescoApiService.peopleApi.addSiteMembershipRequest('-me-', memberBody));
} }
private joinLibrary() {
return this.sitesService.createSiteMembership(this.targetSite.id, {
role: 'SiteConsumer',
id: '-me-'
});
}
private cancelJoinRequest() { private cancelJoinRequest() {
return from(this.alfrescoApiService.peopleApi.removeSiteMembershipRequest('-me-', this.targetSite.id)); return from(this.alfrescoApiService.peopleApi.removeSiteMembershipRequest('-me-', this.targetSite.id));
} }

View File

@@ -33,7 +33,8 @@ import {
NavigateRouteAction, NavigateRouteAction,
SnackbarErrorAction, SnackbarErrorAction,
UpdateLibraryAction, UpdateLibraryAction,
getAppSelection getAppSelection,
ReloadLibraryAction
} from '@alfresco/aca-shared/store'; } from '@alfresco/aca-shared/store';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
@@ -86,6 +87,7 @@ export class LibraryEffects {
} }
}); });
} }
this.store.dispatch(new ReloadLibraryAction());
}) })
); );