[AAE-6242] upload a new version of a file attached in a form (#2524)

* [AAE-8822] Use ADF New Version Uploader dialog instead of using node-version dialog, to allow to upload new file version and to manage file versions

* [AAE-8822] Add versionUpdateDialog unit tests

* [AAE-8822] Add openVersionManagerDialog unit tests

* [AAE-8822] Fix lint problem

* [AAE-8797] Update slector with new adf version uploader dialog class

* [AAE-8797] Remove unused node versions dialog, since the ADF NewVersionUploaderService is used
This commit is contained in:
Amedeo Lepore 2022-06-07 09:48:24 +02:00 committed by GitHub
parent 59878ad3fd
commit 18270d3368
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 157 additions and 375 deletions

View File

@ -38,7 +38,6 @@ import { APP_ROUTES } from './app.routes';
import { FilesComponent } from './components/files/files.component';
import { LibrariesComponent } from './components/libraries/libraries.component';
import { FavoriteLibrariesComponent } from './components/favorite-libraries/favorite-libraries.component';
import { NodeVersionsDialogComponent } from './dialogs/node-versions/node-versions.dialog';
import { AppStoreModule } from './store/app-store.module';
import { MaterialModule } from './material.module';
@ -143,7 +142,6 @@ registerLocaleData(localeSv);
DetailsComponent,
LibrariesComponent,
FavoriteLibrariesComponent,
NodeVersionsDialogComponent,
FavoritesComponent,
RecentFilesComponent,
SharedFilesComponent,

View File

@ -1,33 +0,0 @@
<header mat-dialog-title>{{ data.title | translate }}</header>
<section mat-dialog-content *ngIf="!data.showVersionsOnly">
<adf-version-comparison id="adf-version-comparison" [newFileVersion]="data.file" [node]="data.node"></adf-version-comparison>
<adf-version-upload
id="adf-version-upload-button"
[node]="data.node"
[newFileVersion]="data.file"
[currentVersion]="data.currentVersion"
(success)="handleUpload($event)"
(cancel)="handleCancel()"
(error)="onUploadError($event)"
>
</adf-version-upload>
</section>
<ng-container *ngIf="data.showVersionsOnly">
<section mat-dialog-content>
<div class="adf-version-list-container">
<div class="adf-version-list-table">
<adf-version-list
[node]="data.node"
[showComments]="'adf-version-manager.allowComments' | adfAppConfig: true"
[allowDownload]="'adf-version-manager.allowDownload' | adfAppConfig: true"
(deleted)="refresh($event)"
(restored)="refresh($event)"
(viewVersion)="onViewingVersion($event)"
></adf-version-list>
</div>
</div>
</section>
<footer mat-dialog-actions>
<button mat-button color="primary" [mat-dialog-close]="true">{{ 'VERSION.DIALOG_ADF.CLOSE' | translate }}</button>
</footer>
</ng-container>

View File

@ -1,69 +0,0 @@
.adf-version-manager-dialog-panel-list {
height: 400px;
}
.adf-version-manager-dialog-panel-upload {
height: 500px;
}
.aca-node-versions-dialog {
.mat-dialog-title {
flex: 0 0 auto;
}
.mat-dialog-content {
flex: 1 1 auto;
position: relative;
overflow-y: auto;
}
.mat-dialog-actions {
flex: 0 0 auto;
}
.mat-dialog-title {
font-size: 20px;
font-weight: 600;
font-style: normal;
font-stretch: normal;
line-height: 1.6;
margin: 0;
letter-spacing: -0.5px;
color: var(--theme-text-bold-color);
}
.mat-dialog-actions {
padding: 8px 8px 24px 8px;
display: flex;
justify-content: flex-end;
color: var(--theme-text-color);
button {
text-transform: uppercase;
font-weight: normal;
}
}
.mat-dialog-content {
max-height: 100vh;
overflow: hidden;
padding: 2px 26px;
}
.mat-list-item-content {
padding: 0;
margin: 0 16px;
}
.adf-version-list-container {
.adf-version-list {
height: 250px;
overflow: hidden;
padding: 0;
}
.mat-list.adf-version-list {
overflow: auto;
}
}
}

View File

@ -1,150 +0,0 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NodeVersionsDialogComponent } from './node-versions.dialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreModule } from '@alfresco/adf-core';
import { AppTestingModule } from '../../testing/app-testing.module';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import {
NodeEntityEvent,
UploadVersionButtonComponent,
VersionComparisonComponent,
VersionListComponent,
VersionUploadComponent
} from '@alfresco/adf-content-services';
import { AppStore, UnlockWriteAction, ViewNodeExtras, ViewNodeVersionAction } from '@alfresco/aca-shared/store';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
describe('NodeVersionsDialogComponent', () => {
let fixture: ComponentFixture<NodeVersionsDialogComponent>;
let component: NodeVersionsDialogComponent;
let store: Store<AppStore>;
const node = {
id: '1234',
name: 'TEST-NODE',
isFile: true,
nodeType: 'FAKE',
isFolder: false,
modifiedAt: new Date(),
modifiedByUser: null,
createdAt: new Date(),
createdByUser: null,
content: {
mimeType: 'text/html',
mimeTypeName: 'HTML',
sizeInBytes: 13
}
};
const showVersionsOnly = true;
const file = {
name: 'Fake New file',
type: 'application/pdf',
lastModified: 13,
size: 1351,
slice: null
} as File;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreModule.forRoot(), TranslateModule.forRoot(), MatDialogModule, RouterTestingModule.withRoutes([]), AppTestingModule],
declarations: [
NodeVersionsDialogComponent,
VersionListComponent,
VersionUploadComponent,
UploadVersionButtonComponent,
VersionComparisonComponent
],
providers: [
{
provide: MatDialogRef,
useValue: {
close: jasmine.createSpy('close'),
open: jasmine.createSpy('open')
}
},
{
provide: Store,
useValue: {
dispatch: jasmine.createSpy('dispatch')
}
},
{ provide: MAT_DIALOG_DATA, useValue: { node, showVersionsOnly, file } }
]
});
store = TestBed.inject(Store);
fixture = TestBed.createComponent(NodeVersionsDialogComponent);
component = fixture.componentInstance;
});
it('should display adf upload version if isTypeList is passed as false from parent component', () => {
component.data.showVersionsOnly = false;
fixture.detectChanges();
const adfVersionComponent = document.querySelector('#adf-version-upload-button');
expect(adfVersionComponent).not.toEqual(null);
});
it('should display adf version comparison if isTypeList is passed as false from parent component', () => {
component.data.showVersionsOnly = false;
fixture.detectChanges();
const adfVersionComparisonComponent = document.querySelector('#adf-version-comparison');
expect(adfVersionComparisonComponent).not.toEqual(null);
});
it('should unlock node if is locked when uploading a file', () => {
component.data.showVersionsOnly = false;
const nodeEvent: NodeEntityEvent = new NodeEntityEvent({
entry: {
id: 'a8b2caff-a58c-40f1-8c47-0b8e63ceaa0e',
isFavorite: false,
isFile: true,
isFolder: false,
name: '84348838_3451105884918116_7819187244555567104_o.jpg',
nodeType: 'cm:content',
parentId: '72c65b52-b856-4a5c-b028-42ce03adb4fe',
modifiedAt: null,
createdByUser: null,
createdAt: null,
modifiedByUser: null,
properties: { 'cm:lockType': 'WRITE_LOCK' }
}
});
component.handleUpload(nodeEvent);
expect(store.dispatch).toHaveBeenCalledWith(new UnlockWriteAction(nodeEvent.value));
});
it('should view a previous version of a node', () => {
component.data.showVersionsOnly = false;
const versionId = '1.0';
const location: ViewNodeExtras = {
location: '/'
};
component.onViewingVersion(versionId);
expect(store.dispatch).toHaveBeenCalledWith(new ViewNodeVersionAction(component.data.node.id, versionId, location));
});
});

View File

@ -1,86 +0,0 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { AppStore, SnackbarErrorAction, UnlockWriteAction, ViewNodeVersionAction } from '@alfresco/aca-shared/store';
import { MinimalNodeEntryEntity, Node, Version } from '@alfresco/js-api';
import { Component, EventEmitter, Inject, Output, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { NodeEntityEvent } from '@alfresco/adf-content-services';
import { Router } from '@angular/router';
export interface NodeVersionDialogData {
title: string;
node: MinimalNodeEntryEntity;
file?: File;
currentVersion?: Version;
showVersionsOnly?: boolean;
}
@Component({
templateUrl: './node-versions.dialog.html',
styleUrls: ['./node-versions.dialog.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-node-versions-dialog' }
})
export class NodeVersionsDialogComponent {
/** Emitted when a version is restored or deleted. */
@Output()
refreshEvent = new EventEmitter<Node>();
constructor(
@Inject(MAT_DIALOG_DATA) public data: NodeVersionDialogData,
private store: Store<AppStore>,
private dialogRef: MatDialogRef<NodeVersionsDialogComponent>,
private router: Router
) {}
onUploadError(errorMessage: any) {
this.store.dispatch(new SnackbarErrorAction(errorMessage));
}
handleUpload(nodeEvent: NodeEntityEvent) {
if (nodeEvent.value.entry.properties['cm:lockType'] === 'WRITE_LOCK') {
this.store.dispatch(new UnlockWriteAction(nodeEvent.value));
}
this.dialogRef.close();
}
handleCancel() {
this.dialogRef.close();
}
refresh(node: Node) {
this.refreshEvent.emit(node);
}
onViewingVersion(versionId: string) {
this.store.dispatch(
new ViewNodeVersionAction(this.data.node.id, versionId, {
location: this.router.url
})
);
}
}

View File

@ -41,7 +41,10 @@ import {
SetSelectedNodesAction,
UnlockWriteAction,
SnackbarActionTypes,
NodeActionTypes
NodeActionTypes,
ReloadDocumentListAction,
ViewNodeVersionAction,
ViewNodeExtras
} from '@alfresco/aca-shared/store';
import { map } from 'rxjs/operators';
import { NodeEffects } from '../store/effects/node.effects';
@ -54,7 +57,7 @@ import { TranslationService, AlfrescoApiService, FileModel } from '@alfresco/adf
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar';
import { NodeEntry, Node, VersionPaging } from '@alfresco/js-api';
import { NodeAspectService } from '@alfresco/adf-content-services';
import { NewVersionUploaderDataAction, NewVersionUploaderService, NodeAspectService, ViewVersion } from '@alfresco/adf-content-services';
describe('ContentManagementService', () => {
let dialog: MatDialog;
@ -68,6 +71,7 @@ describe('ContentManagementService', () => {
let alfrescoApiService: AlfrescoApiService;
let nodeAspectService: NodeAspectService;
let appHookService: AppHookService;
let newVersionUploaderService: NewVersionUploaderService;
beforeEach(() => {
TestBed.configureTestingModule({
@ -84,6 +88,7 @@ describe('ContentManagementService', () => {
alfrescoApiService = TestBed.inject(AlfrescoApiService);
nodeAspectService = TestBed.inject(NodeAspectService);
appHookService = TestBed.inject(AppHookService);
newVersionUploaderService = TestBed.inject(NewVersionUploaderService);
dialog = TestBed.inject(MatDialog);
});
@ -1454,28 +1459,110 @@ describe('ContentManagementService', () => {
});
describe('versionUpdateDialog', () => {
let spyOnOpenUploadNewVersionDialog: jasmine.Spy;
let spyOnDispatch: jasmine.Spy;
let fakeFile;
let fakeNode;
beforeEach(() => {
const fakeVersion = { list: { entries: [{ entry: { id: '1.0' } }] } } as VersionPaging;
spyOn(contentApi, 'getNodeVersions').and.returnValue(of(fakeVersion));
});
it('should open dialog with NodeVersionUploadDialogComponent instance', () => {
spyOn(dialog, 'open');
const fakeNode = {
fakeNode = {
name: 'lights.jpg',
id: 'f5e5cb54-200e-41a8-9c21-b5ee77da3992'
};
const fakeFile = new FileModel({ name: 'file1.png', size: 10 } as File, null, 'file1');
contentManagementService.versionUpdateDialog(fakeNode, fakeFile);
expect(dialog.open['calls'].argsFor(0)[0].name).toBe('NodeVersionsDialogComponent');
expect(dialog.open['calls'].argsFor(0)[1].data).toEqual({
node: fakeNode,
file: fakeFile,
currentVersion: { id: '1.0' },
title: 'VERSION.DIALOG.TITLE'
fakeFile = new FileModel({ name: 'file1.png', size: 10 } as File, null, 'file1');
spyOn(contentApi, 'getNodeVersions').and.returnValue(of(fakeVersion));
spyOnDispatch = spyOn(store, 'dispatch');
spyOnOpenUploadNewVersionDialog = spyOn(newVersionUploaderService, 'openUploadNewVersionDialog').and.returnValue(of({ action: 'test' }));
});
it('should open dialog with NewVersionUploaderService', () => {
contentManagementService.versionUpdateDialog(fakeNode, fakeFile);
const expectedParams = [{ node: fakeNode, file: fakeFile, currentVersion: { id: '1.0' }, title: 'VERSION.DIALOG.TITLE' }, { width: '600px' }];
expect(spyOnOpenUploadNewVersionDialog).toHaveBeenCalledOnceWith(...expectedParams);
});
it('should unlock node if is locked when uploading a file', () => {
const mockNewVersion = {
value: {
entry: {
id: 'a8b2caff-a58c-40f1-8c47-0b8e63ceaa0e',
isFavorite: false,
isFile: true,
isFolder: false,
name: '84348838_3451105884918116_7819187244555567104_o.jpg',
nodeType: 'cm:content',
parentId: '72c65b52-b856-4a5c-b028-42ce03adb4fe',
modifiedAt: null,
createdByUser: null,
createdAt: null,
modifiedByUser: null,
properties: { 'cm:lockType': 'WRITE_LOCK' }
}
}
};
spyOnOpenUploadNewVersionDialog.and.returnValue(
of({ action: NewVersionUploaderDataAction.upload, newVersion: mockNewVersion, currentVersion: fakeNode })
);
contentManagementService.versionUpdateDialog(fakeNode, fakeFile);
expect(spyOnDispatch).toHaveBeenCalledOnceWith(new UnlockWriteAction(mockNewVersion.value));
});
it('should unlock node if is locked when uploading a file', () => {
const fakeError = 'Upload error';
spyOnOpenUploadNewVersionDialog.and.returnValue(throwError(fakeError));
contentManagementService.versionUpdateDialog(fakeNode, fakeFile);
expect(spyOnDispatch).toHaveBeenCalledOnceWith(new SnackbarErrorAction(fakeError));
});
});
describe('manageVersions', () => {
let fakeNodeIsFile;
let fakeNodeIsNotFile;
let spyOnOpenUploadNewVersionDialog: jasmine.Spy;
let spyOnDispatch: jasmine.Spy;
beforeEach(() => {
fakeNodeIsFile = { entry: { id: '1', name: 'name1', isFile: true } };
fakeNodeIsNotFile = { entry: { id: '2', name: 'name1', isFile: false } };
spyOnOpenUploadNewVersionDialog = spyOn(newVersionUploaderService, 'openUploadNewVersionDialog').and.returnValue(of({ action: 'test' }));
spyOnDispatch = spyOn(store, 'dispatch');
});
it('should call openUploadNewVersionDialog', () => {
contentManagementService.manageVersions(fakeNodeIsFile);
expect(spyOnOpenUploadNewVersionDialog).toHaveBeenCalledTimes(1);
});
it('should call openUploadNewVersionDialog passing dialog data', () => {
const expectedArgument = {
node: fakeNodeIsFile.entry,
showVersionsOnly: true,
title: 'VERSION.DIALOG.TITLE'
};
contentManagementService.manageVersions(fakeNodeIsFile);
expect(spyOnOpenUploadNewVersionDialog['calls'].argsFor(0)[0]).toEqual(expectedArgument);
});
it('should dispatch ReloadDocumentListAction if dialog emit refresh action', () => {
spyOnOpenUploadNewVersionDialog.and.returnValue(of({ action: NewVersionUploaderDataAction.refresh }));
contentManagementService.manageVersions(fakeNodeIsFile);
expect(spyOnDispatch).toHaveBeenCalledOnceWith(new ReloadDocumentListAction());
});
it('should dispatch ReloadDocumentListAction if dialog emit view action', () => {
const fakeVersionId = '1';
const fakeLocation: ViewNodeExtras = {
location: '/'
};
spyOnOpenUploadNewVersionDialog.and.returnValue(of({ action: NewVersionUploaderDataAction.view, versionId: fakeVersionId } as ViewVersion));
contentManagementService.manageVersions(fakeNodeIsFile);
expect(spyOnDispatch).toHaveBeenCalledOnceWith(new ViewNodeVersionAction(fakeNodeIsFile.entry.id, fakeVersionId, fakeLocation));
});
it('should show permission error is node is not a file and does not have nodeId', () => {
contentManagementService.manageVersions(fakeNodeIsNotFile);
expect(spyOnDispatch).toHaveBeenCalledOnceWith(new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION'));
});
});

View File

@ -40,14 +40,20 @@ import {
SnackbarInfoAction,
SnackbarUserAction,
SnackbarWarningAction,
UndoDeleteNodesAction
UndoDeleteNodesAction,
UnlockWriteAction,
ViewNodeVersionAction
} from '@alfresco/aca-shared/store';
import {
ConfirmDialogComponent,
FolderDialogComponent,
LibraryDialogComponent,
ShareDialogComponent,
NodeAspectService
NodeAspectService,
NewVersionUploaderService,
NewVersionUploaderDialogData,
NewVersionUploaderData,
NewVersionUploaderDataAction
} from '@alfresco/adf-content-services';
import { TranslationService, AlfrescoApiService } from '@alfresco/adf-core';
import {
@ -61,13 +67,13 @@ import {
SiteEntry
} from '@alfresco/js-api';
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Store } from '@ngrx/store';
import { forkJoin, Observable, of, zip } from 'rxjs';
import { catchError, map, mergeMap, take, tap } from 'rxjs/operators';
import { NodeVersionsDialogComponent, NodeVersionDialogData } from '../dialogs/node-versions/node-versions.dialog';
import { NodeActionsService } from './node-actions.service';
import { Router } from '@angular/router';
interface RestoredNode {
status: number;
@ -89,7 +95,9 @@ export class ContentManagementService {
private translation: TranslationService,
private snackBar: MatSnackBar,
private nodeAspectService: NodeAspectService,
private appHookService: AppHookService
private appHookService: AppHookService,
private newVersionUploaderService: NewVersionUploaderService,
private router: Router
) {}
addFavorite(nodes: Array<MinimalNodeEntity>) {
@ -152,11 +160,23 @@ export class ContentManagementService {
versionUpdateDialog(node, file) {
this.contentApi.getNodeVersions(node.id).subscribe(({ list }) => {
this.dialogRef.open(NodeVersionsDialogComponent, {
data: { node, file, currentVersion: list.entries[0].entry, title: 'VERSION.DIALOG.TITLE' } as NodeVersionDialogData,
panelClass: 'adf-version-manager-dialog-panel-upload',
width: '600px'
});
const newVersionUploaderDialogData: NewVersionUploaderDialogData = {
node,
file,
currentVersion: list.entries[0].entry,
title: 'VERSION.DIALOG.TITLE'
};
const dialogConfig: MatDialogConfig = { width: '600px' };
this.newVersionUploaderService.openUploadNewVersionDialog(newVersionUploaderDialogData, dialogConfig).subscribe(
(data: NewVersionUploaderData) => {
if (data.action === NewVersionUploaderDataAction.upload) {
if (data.newVersion.value.entry.properties['cm:lockType'] === 'WRITE_LOCK') {
this.store.dispatch(new UnlockWriteAction(data.newVersion.value));
}
}
},
(error) => this.store.dispatch(new SnackbarErrorAction(error))
);
});
}
@ -551,13 +571,28 @@ export class ContentManagementService {
private openVersionManagerDialog(node: any) {
// workaround Shared
if (node.isFile || node.nodeId) {
const dialogRef = this.dialogRef.open(NodeVersionsDialogComponent, {
data: { node, showVersionsOnly: true, title: 'VERSION.DIALOG_ADF.TITLE' } as NodeVersionDialogData,
panelClass: 'adf-version-manager-dialog-panel',
width: '630px'
});
dialogRef.componentInstance.refreshEvent.subscribe(() => {
const newVersionUploaderDialogData: NewVersionUploaderDialogData = {
node,
showVersionsOnly: true,
title: 'VERSION.DIALOG.TITLE'
};
this.newVersionUploaderService
.openUploadNewVersionDialog(newVersionUploaderDialogData, { width: '630px' })
.subscribe((newVersionUploaderData: NewVersionUploaderData) => {
switch (newVersionUploaderData.action) {
case NewVersionUploaderDataAction.refresh:
this.store.dispatch(new ReloadDocumentListAction());
break;
case NewVersionUploaderDataAction.view:
this.store.dispatch(
new ViewNodeVersionAction(node.id, newVersionUploaderData.versionId, {
location: this.router.url
})
);
break;
default:
break;
}
});
} else {
this.store.dispatch(new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION'));

View File

@ -34,7 +34,7 @@ export class ManageVersionsDialog extends GenericDialog {
menu = new Menu();
constructor() {
super('.aca-node-versions-dialog');
super('.adf-new-version-uploader-dialog');
}
async clickClose(): Promise<void> {

View File

@ -35,7 +35,7 @@ export class UploadNewVersionDialog extends GenericDialog {
description = this.childElement(by.css('textarea'));
constructor() {
super('.adf-version-manager-dialog-panel-upload');
super('.adf-new-version-uploader-dialog');
}
async isCancelButtonEnabled(): Promise<boolean> {