[ADF-2540] Lock node feature (#3138)

* add adf-node-lock directive

* add lock-node service + button in context menu

* unit tests

* docs

* unit tests fix

* Remove unnecessary imports

* PR changes

* Remove fit from tests

* Update specific node from list on lock/ulock
This commit is contained in:
Alex Bolboșenco
2018-04-06 08:59:28 +03:00
committed by Denys Vuika
parent 7b7e39d989
commit 7d1b4bf14a
26 changed files with 643 additions and 29 deletions

View File

@@ -21,32 +21,43 @@ import { MaterialModule } from '../material.module';
import { DownloadZipDialogComponent } from './download-zip.dialog';
import { FolderDialogComponent } from './folder.dialog';
import { NodeLockDialogComponent } from './node-lock.dialog';
import { ShareDialogComponent } from './share.dialog';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { FormModule } from '@alfresco/adf-core';
import { MatDatetimepickerModule } from '@mat-datetimepicker/core';
import { MatMomentDatetimeModule } from '@mat-datetimepicker/moment';
@NgModule({
imports: [
CommonModule,
MaterialModule,
TranslateModule,
FormsModule,
ReactiveFormsModule
FormModule,
ReactiveFormsModule,
MatMomentDatetimeModule,
MatDatetimepickerModule
],
declarations: [
DownloadZipDialogComponent,
FolderDialogComponent,
NodeLockDialogComponent,
ShareDialogComponent
],
exports: [
DownloadZipDialogComponent,
FolderDialogComponent,
NodeLockDialogComponent,
ShareDialogComponent
],
entryComponents: [
DownloadZipDialogComponent,
FolderDialogComponent,
NodeLockDialogComponent,
ShareDialogComponent
]
})

View File

@@ -0,0 +1,47 @@
<h2 mat-dialog-title>
{{ 'CORE.FILE_DIALOG.FILE_LOCK' | translate }}
</h2>
<mat-dialog-content>
<br />
<form [formGroup]="form" (submit)="submit()">
<mat-checkbox [formControl]="form.controls['isLocked']" ngDefaultControl>
{{ 'CORE.FILE_DIALOG.FILE_LOCK_CHECKBOX' | translate }} <strong>"{{ nodeName }}"</strong>
</mat-checkbox>
<br />
<div *ngIf="form.value.isLocked">
<mat-checkbox [formControl]="form.controls['allowOwner']" ngDefaultControl>
{{ 'CORE.FILE_DIALOG.ALLOW_OTHERS_CHECKBOX' | translate }}
</mat-checkbox>
<br />
<mat-checkbox [formControl]="form.controls['isTimeLock']" ngDefaultControl>
{{ 'CORE.FILE_DIALOG.TIME_LOCK_CHECKBOX' | translate }}
</mat-checkbox>
<br />
<mat-form-field *ngIf="form.value.isTimeLock">
<mat-datetimepicker-toggle [for]="datetimePicker" matSuffix></mat-datetimepicker-toggle>
<mat-datetimepicker #datetimePicker type="datetime" openOnFocus="true" timeInterval="1"></mat-datetimepicker>
<input matInput [formControl]="form.controls['time']" [matDatetimepicker]="datetimePicker" required autocomplete="false">
</mat-form-field>
</div>
</form>
<br />
</mat-dialog-content>
<mat-dialog-actions class="adf-dialog-buttons">
<span class="adf-fill-remaining-space"></span>
<button mat-button mat-dialog-close>
{{ 'CORE.FILE_DIALOG.CANCEL_BUTTON.LABEL' | translate }}
</button>
<button class="adf-dialog-action-button" mat-button (click)="submit()">
{{ 'CORE.FILE_DIALOG.SAVE_BUTTON.LABEL' | translate }}
</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,161 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import moment from 'moment-es6';
import { async, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { ComponentFixture } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatDialogRef } from '@angular/material';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { Observable } from 'rxjs/Observable';
import { AlfrescoApiService, TranslationService } from '@alfresco/adf-core';
import { NodeLockDialogComponent } from './node-lock.dialog';
describe('NodeLockDialogComponent', () => {
let fixture: ComponentFixture<NodeLockDialogComponent>;
let component: NodeLockDialogComponent;
let translationService: TranslationService;
let alfrescoApi: AlfrescoApiService;
let expiryDate;
const dialogRef = {
close: jasmine.createSpy('close')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
BrowserDynamicTestingModule
],
declarations: [
NodeLockDialogComponent
],
providers: [
{ provide: MatDialogRef, useValue: dialogRef }
]
});
TestBed.overrideModule(BrowserDynamicTestingModule, {
set: { entryComponents: [NodeLockDialogComponent] }
});
TestBed.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NodeLockDialogComponent);
component = fixture.componentInstance;
alfrescoApi = TestBed.get(AlfrescoApiService);
translationService = TestBed.get(TranslationService);
spyOn(translationService, 'get').and.returnValue(Observable.of('message'));
});
describe('Node lock dialog component', () => {
beforeEach(() => {
jasmine.clock().mockDate(new Date());
expiryDate = moment().add(1, 'minutes');
component.data = {
node: {
id: 'node-id',
name: 'node-name',
isLocked: true,
properties: {
['cm:lockType']: 'WRITE_LOCK',
['cm:expiryDate']: expiryDate
}
},
onError: () => {}
};
fixture.detectChanges();
});
it('should init dialog with form inputs', () => {
expect(component.nodeName).toBe('node-name');
expect(component.form.value.isLocked).toBe(true);
expect(component.form.value.allowOwner).toBe(true);
expect(component.form.value.isTimeLock).toBe(true);
expect(component.form.value.time.format()).toBe(expiryDate.format());
});
it('should update form inputs', () => {
let newTime = moment();
component.form.controls['isLocked'].setValue(false);
component.form.controls['allowOwner'].setValue(false);
component.form.controls['isTimeLock'].setValue(false);
component.form.controls['time'].setValue(newTime);
expect(component.form.value.isLocked).toBe(false);
expect(component.form.value.allowOwner).toBe(false);
expect(component.form.value.isTimeLock).toBe(false);
expect(component.form.value.time.format()).toBe(newTime.format());
});
it('should submit the form and lock the node', () => {
spyOn(alfrescoApi.nodesApi, 'lockNode').and.returnValue(Promise.resolve({}));
component.submit();
expect(alfrescoApi.nodesApi.lockNode).toHaveBeenCalledWith(
'node-id',
{
'timeToExpire': 60,
'type': 'ALLOW_OWNER_CHANGES',
'lifetime': 'PERSISTENT'
}
);
});
it('should submit the form and unlock the node', () => {
spyOn(alfrescoApi.nodesApi, 'unlockNode').and.returnValue(Promise.resolve({}));
component.form.controls['isLocked'].setValue(false);
component.submit();
expect(alfrescoApi.nodesApi.unlockNode).toHaveBeenCalledWith('node-id');
});
it('should call dialog to close with form data when submit is succesfluly', fakeAsync(() => {
const node = { entry: {} };
spyOn(alfrescoApi.nodesApi, 'lockNode').and.returnValue(Promise.resolve(node));
component.submit();
tick();
fixture.detectChanges();
expect(dialogRef.close).toHaveBeenCalledWith(node.entry);
}));
it('should call onError if submit fails', fakeAsync(() => {
spyOn(alfrescoApi.nodesApi, 'lockNode').and.returnValue(Promise.reject('error'));
spyOn(component.data, 'onError');
component.submit();
tick();
fixture.detectChanges();
expect(component.data.onError).toHaveBeenCalled();
}));
});
});

View File

@@ -0,0 +1,94 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import moment from 'moment-es6';
import { Component, Inject, OnInit, Optional } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { FormBuilder, FormGroup } from '@angular/forms';
import { MinimalNodeEntryEntity, NodeEntry } from 'alfresco-js-api';
import { AlfrescoApiService } from '@alfresco/adf-core';
@Component({
selector: 'adf-node-lock',
styleUrls: ['./folder.dialog.scss'],
templateUrl: './node-lock.dialog.html'
})
export class NodeLockDialogComponent implements OnInit {
form: FormGroup;
node: MinimalNodeEntryEntity = null;
nodeName: string;
constructor(
private formBuilder: FormBuilder,
public dialog: MatDialogRef<NodeLockDialogComponent>,
private alfrescoApi: AlfrescoApiService,
@Optional()
@Inject(MAT_DIALOG_DATA)
public data: any
) {}
ngOnInit() {
const { node } = this.data;
this.nodeName = node.name;
this.form = this.formBuilder.group({
isLocked: node.isLocked || false,
allowOwner: node.properties['cm:lockType'] === 'WRITE_LOCK',
isTimeLock: !!node.properties['cm:expiryDate'],
time: !!node.properties['cm:expiryDate'] ? moment(node.properties['cm:expiryDate']) : moment()
});
}
private get lockTimeInSeconds(): number {
if (this.form.value.isTimeLock) {
let duration = moment.duration(moment(this.form.value.time).diff(moment()));
return duration.asSeconds();
}
return 0;
}
private get nodeBodyLock(): object {
return {
'timeToExpire': this.lockTimeInSeconds,
'type': this.form.value.allowOwner ? 'ALLOW_OWNER_CHANGES' : 'FULL',
'lifetime': 'PERSISTENT'
};
}
private toggleLock(): Promise<NodeEntry> {
const { alfrescoApi: { nodesApi }, data: { node } } = this;
if (this.form.value.isLocked) {
return nodesApi.lockNode(node.id, this.nodeBodyLock);
}
return nodesApi.unlockNode(node.id);
}
submit(): void {
this.toggleLock()
.then(node => {
this.data.node.isLocked = this.form.value.isLocked;
this.dialog.close(node.entry);
})
.catch(error => this.data.onError(error));
}
}

View File

@@ -17,4 +17,5 @@
export * from './download-zip.dialog';
export * from './folder.dialog';
export * from './node-lock.dialog';
export * from './share.dialog';