mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ACA-3729] Join library action for Admin Users (#1801)
This commit is contained in:
@@ -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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
@@ -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());
|
||||||
|
|
||||||
|
@@ -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]));
|
||||||
|
@@ -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();
|
||||||
|
|
||||||
|
@@ -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'));
|
||||||
|
@@ -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));
|
||||||
}
|
}
|
||||||
|
@@ -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());
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user