From 8975d4b6a654871e235007d8a3f51b07da1b5f82 Mon Sep 17 00:00:00 2001 From: Cilibiu Bogdan Date: Wed, 10 Apr 2019 18:30:50 +0300 Subject: [PATCH] [ADF-4371] Versioning - revert upload version on delete (#4572) * revert upload version on delete * methods return type --- .../upload/remove-upload.e2e.ts | 101 +++++++++++ lib/content-services/i18n/en.json | 3 +- .../file-uploading-list-row.component.html | 12 +- .../file-uploading-list-row.component.spec.ts | 37 ++-- .../file-uploading-list-row.component.ts | 22 +++ .../file-uploading-list.component.spec.ts | 28 ++- .../file-uploading-list.component.ts | 161 ++++++++++++------ lib/core/services/thumbnail.service.ts | 1 + 8 files changed, 300 insertions(+), 65 deletions(-) create mode 100644 e2e/content-services/upload/remove-upload.e2e.ts diff --git a/e2e/content-services/upload/remove-upload.e2e.ts b/e2e/content-services/upload/remove-upload.e2e.ts new file mode 100644 index 0000000000..60100b4fb4 --- /dev/null +++ b/e2e/content-services/upload/remove-upload.e2e.ts @@ -0,0 +1,101 @@ +/*! + * @license + * Copyright 2019 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 { LoginPage } from '@alfresco/adf-testing'; +import { ContentServicesPage } from '../../pages/adf/contentServicesPage'; +import { UploadDialog } from '../../pages/adf/dialog/uploadDialog'; +import { VersionManagePage } from '../../pages/adf/versionManagerPage'; + +import { AcsUserModel } from '../../models/ACS/acsUserModel'; +import { FileModel } from '../../models/ACS/fileModel'; + +import TestConfig = require('../../test.config'); +import resources = require('../../util/resources'); + +import { AlfrescoApiCompatibility as AlfrescoApi } from '@alfresco/js-api'; +import { browser } from 'protractor'; + +describe('Upload component', () => { + const contentServicesPage = new ContentServicesPage(); + const uploadDialog = new UploadDialog(); + const versionManagePage = new VersionManagePage(); + const loginPage = new LoginPage(); + const acsUser = new AcsUserModel(); + + const docxFileModel = new FileModel({ + name: resources.Files.ADF_DOCUMENTS.DOCX_SUPPORTED.file_name, + location: resources.Files.ADF_DOCUMENTS.DOCX_SUPPORTED.file_location + }); + + const fileModelVersion = new FileModel({ + 'name': resources.Files.ADF_DOCUMENTS.PNG.file_name, + 'location': resources.Files.ADF_DOCUMENTS.PNG.file_location + }); + + beforeAll(async (done) => { + this.alfrescoJsApi = new AlfrescoApi({ + provider: 'ECM', + hostEcm: TestConfig.adf.url + }); + + await this.alfrescoJsApi.login( + TestConfig.adf.adminEmail, + TestConfig.adf.adminPassword + ); + + await this.alfrescoJsApi.core.peopleApi.addPerson(acsUser); + + await this.alfrescoJsApi.login(acsUser.id, acsUser.password); + + loginPage.loginToContentServicesUsingUserModel(acsUser); + + contentServicesPage.goToDocumentList(); + + done(); + }); + + beforeEach(() => { + contentServicesPage.goToDocumentList(); + }); + + it('should remove uploaded file', () => { + contentServicesPage.uploadFile(docxFileModel.location); + uploadDialog.fileIsUploaded(docxFileModel.name); + uploadDialog + .removeUploadedFile(docxFileModel.name) + .fileIsCancelled(docxFileModel.name) + .clickOnCloseButton(); + }); + + it('should revert to last version when remove uploaded version file', () => { + contentServicesPage.uploadFile(docxFileModel.location); + uploadDialog.fileIsUploaded(docxFileModel.name); + contentServicesPage.checkContentIsDisplayed(docxFileModel.name); + + contentServicesPage.versionManagerContent(docxFileModel.name); + versionManagePage.showNewVersionButton.click(); + versionManagePage.uploadNewVersionFile( + fileModelVersion.location + ); + versionManagePage.closeVersionDialog(); + uploadDialog + .removeUploadedFile(fileModelVersion.name) + .fileIsCancelled(fileModelVersion.name); + browser.refresh(); + contentServicesPage.checkContentIsDisplayed(docxFileModel.name); + }); +}); diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json index 17022cb032..bfbfd692ae 100644 --- a/lib/content-services/i18n/en.json +++ b/lib/content-services/i18n/en.json @@ -161,7 +161,8 @@ "500": "Internal server error, try again or contact IT support [500]", "504": "The server timed out, try again or contact IT support [504]", "403": "Insufficient permissions to upload in this location [403]", - "404": "Upload location no longer exists [404]" + "404": "Upload location no longer exists [404]", + "409": "A file with the same name already exists [409]" }, "ARIA-LABEL": { "ERROR": "Upload error" diff --git a/lib/content-services/upload/components/file-uploading-list-row.component.html b/lib/content-services/upload/components/file-uploading-list-row.component.html index 192491d5d3..1a50d171d2 100644 --- a/lib/content-services/upload/components/file-uploading-list-row.component.html +++ b/lib/content-services/upload/components/file-uploading-list-row.component.html @@ -1,16 +1,22 @@
- + insert_drive_file + + {{ file.name }} + + {{ + versionNumber + }} + +
{ beforeEach(() => { fixture = TestBed.createComponent(FileUploadingListRowComponent); component = fixture.componentInstance; - component.file = file; }); - it('emits cancel event', () => { - spyOn(component.cancel, 'emit'); - component.onCancel(component.file); + describe('events', () => { + beforeEach(() => { + component.file = file; + }); - expect(component.cancel.emit).toHaveBeenCalledWith(file); + it('should emit cancel event', () => { + spyOn(component.cancel, 'emit'); + component.onCancel(component.file); + + expect(component.cancel.emit).toHaveBeenCalledWith(file); + }); + + it('should emit remove event', () => { + spyOn(component.remove, 'emit'); + component.onRemove(component.file); + + expect(component.remove.emit).toHaveBeenCalledWith(file); + }); }); - it('emits remove event', () => { - spyOn(component.remove, 'emit'); - component.onRemove(component.file); + it('should render node version when upload a version file', () => { + component.file = new FileModel( { name: 'fake-name' }); + component.file.options = { newVersion: true }; + component.file.data = { entry: { properties: { 'cm:versionLabel': '1' } } }; - expect(component.remove.emit).toHaveBeenCalledWith(file); + fixture.detectChanges(); + + expect(fixture.nativeElement.querySelector( + '.adf-file-uploading-row__version' + ).textContent).toContain('1'); }); }); diff --git a/lib/content-services/upload/components/file-uploading-list-row.component.ts b/lib/content-services/upload/components/file-uploading-list-row.component.ts index 04ce69ad89..4ab520c834 100644 --- a/lib/content-services/upload/components/file-uploading-list-row.component.ts +++ b/lib/content-services/upload/components/file-uploading-list-row.component.ts @@ -48,4 +48,26 @@ export class FileUploadingListRowComponent { this.file.status === FileUploadStatus.Aborted || this.file.status === FileUploadStatus.Deleted; } + + get versionNumber(): string { + return this.file.data.entry.properties['cm:versionLabel']; + } + + get mimeType(): string { + if (this.file && this.file.file && this.file.file.type) { + return this.file.file.type; + } + + return 'default'; + } + + isUploadVersion(): boolean { + return ( + !!this.file.data && + this.file.options && + this.file.options.newVersion && + this.file.data.entry.properties && + this.file.data.entry.properties['cm:versionLabel'] + ); + } } diff --git a/lib/content-services/upload/components/file-uploading-list.component.spec.ts b/lib/content-services/upload/components/file-uploading-list.component.spec.ts index 4afa46c18e..c2e5789511 100644 --- a/lib/content-services/upload/components/file-uploading-list.component.spec.ts +++ b/lib/content-services/upload/components/file-uploading-list.component.spec.ts @@ -17,7 +17,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslationService, FileUploadStatus, NodesApiService, UploadService, - setupTestBed, CoreModule, AlfrescoApiService, AlfrescoApiServiceMock + setupTestBed, CoreModule, AlfrescoApiService, AlfrescoApiServiceMock, FileModel, FileUploadOptions } from '@alfresco/adf-core'; import { of, throwError } from 'rxjs'; import { UploadModule } from '../upload.module'; @@ -30,6 +30,7 @@ describe('FileUploadingListComponent', () => { let uploadService: UploadService; let nodesApiService: NodesApiService; let translateService: TranslationService; + let alfrescoApiService: AlfrescoApiService; let file: any; beforeEach(() => { @@ -55,6 +56,7 @@ describe('FileUploadingListComponent', () => { translateService = TestBed.get(TranslationService); fixture = TestBed.createComponent(FileUploadingListComponent); + alfrescoApiService = TestBed.get(AlfrescoApiService); component = fixture.componentInstance; spyOn(translateService, 'get').and.returnValue(of('some error message')); @@ -106,6 +108,30 @@ describe('FileUploadingListComponent', () => { expect(uploadService.cancelUpload).toHaveBeenCalled(); }); + it('should delete node version', () => { + spyOn(alfrescoApiService.versionsApi, 'deleteVersion').and.returnValue(of(file)); + file = new FileModel( { name: 'fake-name' }); + file.options = { newVersion: true }; + file.data = { entry: { id: 'nodeId', properties: { 'cm:versionLabel': '1' } } }; + + component.removeFile(file); + + expect(alfrescoApiService.versionsApi.deleteVersion).toHaveBeenCalled(); + }); + + it('should throw error when delete node version fails', (done) => { + spyOn(alfrescoApiService.versionsApi, 'deleteVersion').and.returnValue(throwError(file)); + file = new FileModel( { name: 'fake-name' }); + file.options = { newVersion: true }; + file.data = { entry: { id: 'nodeId', properties: { 'cm:versionLabel': '1' } } }; + + component.error.subscribe(() => { + done(); + }); + + component.removeFile(file); + }); + describe('Events', () => { it('should throw an error event if delete file goes wrong', (done) => { diff --git a/lib/content-services/upload/components/file-uploading-list.component.ts b/lib/content-services/upload/components/file-uploading-list.component.ts index 054aa03a3c..c1954a11c8 100644 --- a/lib/content-services/upload/components/file-uploading-list.component.ts +++ b/lib/content-services/upload/components/file-uploading-list.component.ts @@ -15,9 +15,23 @@ * limitations under the License. */ -import { FileModel, FileUploadStatus, NodesApiService, TranslationService, UploadService } from '@alfresco/adf-core'; -import { Component, ContentChild, Input, Output, TemplateRef, EventEmitter } from '@angular/core'; -import { Observable, forkJoin, of } from 'rxjs'; +import { + FileModel, + FileUploadStatus, + NodesApiService, + AlfrescoApiService, + TranslationService, + UploadService +} from '@alfresco/adf-core'; +import { + Component, + ContentChild, + Input, + Output, + TemplateRef, + EventEmitter +} from '@angular/core'; +import { Observable, forkJoin, of, from } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; @Component({ @@ -26,7 +40,6 @@ import { map, catchError } from 'rxjs/operators'; styleUrls: ['./file-uploading-list.component.scss'] }) export class FileUploadingListComponent { - FileUploadStatus = FileUploadStatus; @ContentChild(TemplateRef) @@ -40,10 +53,11 @@ export class FileUploadingListComponent { error: EventEmitter = new EventEmitter(); constructor( + private alfrescoApiService: AlfrescoApiService, private uploadService: UploadService, private nodesApi: NodesApiService, - private translateService: TranslationService) { - } + private translateService: TranslationService + ) {} /** * Cancel file upload @@ -56,100 +70,147 @@ export class FileUploadingListComponent { this.uploadService.cancelUpload(file); } + /** + * Remove uploaded file + * + * @param file File model to remove upload for. + * + * @memberOf FileUploadingListComponent + */ removeFile(file: FileModel): void { - this.deleteNode(file) - .subscribe(() => { - if ( file.status === FileUploadStatus.Error) { + if (file.options && file.options.newVersion) { + this.deleteNodeVersion(file).subscribe(() => { + if (file.status === FileUploadStatus.Error) { + this.notifyError(file); + } + this.uploadService.cancelUpload(file); + }); + } else { + this.deleteNode(file).subscribe(() => { + if (file.status === FileUploadStatus.Error) { this.notifyError(file); } + this.cancelNodeVersionInstances(file); this.uploadService.cancelUpload(file); }); + } } /** * Call the appropriate method for each file, depending on state */ cancelAllFiles(): void { - this.getUploadingFiles() - .forEach((file) => this.uploadService.cancelUpload(file)); + this.getUploadingFiles().forEach((file) => + this.uploadService.cancelUpload(file) + ); const deletedFiles = this.files .filter((file) => file.status === FileUploadStatus.Complete) .map((file) => this.deleteNode(file)); - forkJoin(...deletedFiles) - .subscribe((files: FileModel[]) => { - const errors = files - .filter((file) => file.status === FileUploadStatus.Error); + forkJoin(...deletedFiles).subscribe((files: FileModel[]) => { + const errors = files.filter( + (file) => file.status === FileUploadStatus.Error + ); - if (errors.length) { - this.notifyError(...errors); - } + if (errors.length) { + this.notifyError(...errors); + } - this.uploadService.cancelUpload(...files); - }); + this.uploadService.cancelUpload(...files); + }); } /** * Checks if all the files are uploaded false if there is at least one file in Progress | Starting | Pending */ isUploadCompleted(): boolean { - return !this.isUploadCancelled() && + return ( + !this.isUploadCancelled() && Boolean(this.files.length) && - !this.files - .some(({status}) => + !this.files.some( + ({ status }) => status === FileUploadStatus.Starting || status === FileUploadStatus.Progress || status === FileUploadStatus.Pending - ); + ) + ); } /** * Check if all the files are Cancelled | Aborted | Error. false if there is at least one file in uploading states */ isUploadCancelled(): boolean { - return !!this.files.length && - this.files - .every(({status}) => + return ( + !!this.files.length && + this.files.every( + ({ status }) => status === FileUploadStatus.Aborted || status === FileUploadStatus.Cancelled || status === FileUploadStatus.Deleted - ); + ) + ); } private deleteNode(file: FileModel): Observable { const { id } = file.data.entry; - return this.nodesApi - .deleteNode(id, { permanent: true }) - .pipe( - map(() => { - file.status = FileUploadStatus.Deleted; - return file; - }), - catchError(() => { - file.status = FileUploadStatus.Error; - return of(file); - }) - ); + return this.nodesApi.deleteNode(id, { permanent: true }).pipe( + map(() => { + file.status = FileUploadStatus.Deleted; + return file; + }), + catchError(() => { + file.status = FileUploadStatus.Error; + return of(file); + }) + ); + } + + private deleteNodeVersion(file: FileModel): Observable { + return from( + this.alfrescoApiService.versionsApi.deleteVersion( + file.data.entry.id, + file.data.entry.properties['cm:versionLabel'] + ) + ).pipe( + map(() => { + file.status = FileUploadStatus.Deleted; + return file; + }), + catchError(() => { + file.status = FileUploadStatus.Error; + return of(file); + }) + ); + } + + private cancelNodeVersionInstances(file) { + this.files + .filter( + (item) => + item.data.entry.id === file.data.entry.id && + item.options.newVersion + ) + .map((item) => { + item.status = FileUploadStatus.Deleted; + }); } private notifyError(...files: FileModel[]) { let messageError: string = null; if (files.length === 1) { - messageError = this.translateService - .instant( - 'FILE_UPLOAD.MESSAGES.REMOVE_FILE_ERROR', - { fileName: files[0].name} - ); + messageError = this.translateService.instant( + 'FILE_UPLOAD.MESSAGES.REMOVE_FILE_ERROR', + { fileName: files[0].name } + ); } else { - messageError = this.translateService - .instant( - 'FILE_UPLOAD.MESSAGES.REMOVE_FILES_ERROR', - { total: files.length } - ); + messageError = this.translateService.instant( + 'FILE_UPLOAD.MESSAGES.REMOVE_FILES_ERROR', + { total: files.length } + ); } this.error.emit(messageError); diff --git a/lib/core/services/thumbnail.service.ts b/lib/core/services/thumbnail.service.ts index e211e1e163..eb7e181a33 100644 --- a/lib/core/services/thumbnail.service.ts +++ b/lib/core/services/thumbnail.service.ts @@ -84,6 +84,7 @@ export class ThumbnailService { 'application/vnd.sun.xml.writer': './assets/images/ft_ic_ms_word.svg', 'application/vnd.sun.xml.writer.template': './assets/images/ft_ic_ms_word.svg', 'application/rtf': './assets/images/ft_ic_ms_word.svg', + 'text/rtf': './assets/images/ft_ic_ms_word.svg', 'application/vnd.ms-powerpoint': './assets/images/ft_ic_ms_powerpoint.svg', 'application/vnd.openxmlformats-officedocument.presentationml.presentation': './assets/images/ft_ic_ms_powerpoint.svg', 'application/vnd.openxmlformats-officedocument.presentationml.template': './assets/images/ft_ic_ms_powerpoint.svg',