From 9bb7d90670a1bc8e80b05b4060a988a812acb861 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Fri, 26 May 2017 14:39:36 +0100 Subject: [PATCH] [ADF-610] Upload button and DnD area should not upload hidden files and folders (#1908) [ADF-610] upload cleanup - more strongly typing - api improvements * Upload cleanup and api improvements - remove old unused settings (formFields variable) - individual options for uploaded files (i.e. versioning) - upload button and drag-and-drop area now set individual settings for file versioning * exclude hidden files from upload --- .../file-uploading-dialog.component.spec.ts | 7 +-- .../src/components/upload-button.component.ts | 33 ++++------ .../upload-drag-area.component.spec.ts | 8 ++- .../components/upload-drag-area.component.ts | 29 +++------ .../src/models/file.model.ts | 26 ++++---- .../src/services/upload.service.spec.ts | 62 +++++++----------- .../src/services/upload.service.ts | 63 +++++++------------ 7 files changed, 90 insertions(+), 138 deletions(-) diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.spec.ts b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.spec.ts index 19b4fe6677..9f80049247 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.spec.ts @@ -50,10 +50,7 @@ describe('FileUploadingDialogComponent', () => { beforeEach(() => { window['componentHandler'] = null; - let fileFake = { - id: 'fake-id', - name: 'fake-name' - }; + const fileFake = new File([''], 'fake-name'); file = new FileModel(fileFake); fixture = TestBed.createComponent(FileUploadingDialogComponent); @@ -80,7 +77,7 @@ describe('FileUploadingDialogComponent', () => { }); it('should render dialog box with css class show when an element is added to Observer', () => { - uploadService.addToQueue([ { name: 'file' }]); + uploadService.addToQueue(new FileModel( { name: 'file' })); component.filesUploadingList = [file]; fixture.detectChanges(); diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts index 4256157030..05bdd0cc97 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts @@ -127,8 +127,6 @@ export class UploadButtonComponent implements OnInit, OnChanges { if (rootFolderId && rootFolderId.currentValue) { this.checkPermission(); } - let formFields = this.createFormFields(); - this.uploadService.setOptions(formFields, this.versioning); } isButtonDisabled(): boolean { @@ -186,7 +184,7 @@ export class UploadButtonComponent implements OnInit, OnChanges { this.onError.emit({value: errorMessagePlaceholder}); let errorMessage = this.formatString(errorMessagePlaceholder, [directoryName]); if (errorMessage) { - this._showErrorNotificationBar(errorMessage); + this.showErrorNotificationBar(errorMessage); } } } @@ -204,12 +202,13 @@ export class UploadButtonComponent implements OnInit, OnChanges { * @param path * @param files */ - uploadFiles(path: string, files: any[]) { + uploadFiles(path: string, files: File[]): void { if (files.length) { - let latestFilesAdded = this.uploadService.addToQueue(files); + const latestFilesAdded = files.map(f => new FileModel(f, { newVersion: this.versioning })); + this.uploadService.addToQueue(...latestFilesAdded); this.uploadService.uploadFilesInTheQueue(this.rootFolderId, path, this.onSuccess); if (this.showNotificationBar) { - this._showUndoNotificationBar(latestFilesAdded); + this.showUndoNotificationBar(latestFilesAdded); } } } @@ -220,8 +219,8 @@ export class UploadButtonComponent implements OnInit, OnChanges { * @param files - array of files * @returns {Map} */ - private convertIntoHashMap(files: any[]) { - let directoryMap = new Map(); + private convertIntoHashMap(files: File[]): Map { + let directoryMap = new Map(); for (let file of files) { let directory = this.getDirectoryPath(file.webkitRelativePath); let filesSomeDir = directoryMap.get(directory) || []; @@ -236,7 +235,7 @@ export class UploadButtonComponent implements OnInit, OnChanges { * @param directory * @returns {string} */ - private getDirectoryPath(directory: string) { + private getDirectoryPath(directory: string): string { let relativeDirPath = ''; let dirPath = directory.split('/'); if (dirPath.length > 1) { @@ -251,7 +250,7 @@ export class UploadButtonComponent implements OnInit, OnChanges { * @param directory * @returns {string} */ - private getDirectoryName(directory: string) { + private getDirectoryName(directory: string): string { let dirPath = directory.split('/'); if (dirPath.length > 1) { return dirPath.pop(); @@ -265,7 +264,7 @@ export class UploadButtonComponent implements OnInit, OnChanges { * * @param {FileModel[]} latestFilesAdded - files in the upload queue enriched with status flag and xhr object. */ - private _showUndoNotificationBar(latestFilesAdded: FileModel[]) { + private showUndoNotificationBar(latestFilesAdded: FileModel[]): void { let messageTranslate: any, actionTranslate: any; messageTranslate = this.translateService.get('FILE_UPLOAD.MESSAGES.PROGRESS'); actionTranslate = this.translateService.get('FILE_UPLOAD.ACTION.UNDO'); @@ -295,7 +294,7 @@ export class UploadButtonComponent implements OnInit, OnChanges { * @param Error message * @private */ - private _showErrorNotificationBar(errorMessage: string) { + private showErrorNotificationBar(errorMessage: string): void { this.notificationService.openSnackMessage(errorMessage, 3000); } @@ -305,7 +304,7 @@ export class UploadButtonComponent implements OnInit, OnChanges { * @param keys - array of value * @returns {string} - The message without placeholder */ - private formatString(message: string, keys: any []) { + private formatString(message: string, keys: any []): string { let i = keys.length; while (i--) { message = message.replace(new RegExp('\\{' + i + '\\}', 'gm'), keys[i]); @@ -313,14 +312,6 @@ export class UploadButtonComponent implements OnInit, OnChanges { return message; } - private createFormFields(): any { - return { - formFields: { - overwrite: true - } - }; - } - checkPermission() { if (this.rootFolderId) { this.uploadService.getFolderNode(this.rootFolderId).subscribe( diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.spec.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.spec.ts index f117f261b3..be169a5b5d 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.spec.ts @@ -22,6 +22,7 @@ import { AlfrescoTranslationService, CoreModule, LogService, LogServiceMock, Not import { UploadDragAreaComponent } from './upload-drag-area.component'; import { TranslationMock } from '../assets/translation.service.mock'; import { UploadService } from '../services/upload.service'; +import { FileModel } from '../models/file.model'; describe('UploadDragAreaComponent', () => { @@ -73,11 +74,12 @@ describe('UploadDragAreaComponent', () => { uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue'); fixture.detectChanges(); - let fileFake = {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'}; + const file = {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'}; + let fileFake = new FileModel(file); let filesList = [fileFake]; component.onFilesDropped(filesList); - expect(uploadService.addToQueue).toHaveBeenCalledWith(filesList); + expect(uploadService.addToQueue).toHaveBeenCalledWith(fileFake); expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/folder-fake', null); }); @@ -89,7 +91,7 @@ describe('UploadDragAreaComponent', () => { component.showUndoNotificationBar = jasmine.createSpy('_showUndoNotificationBar'); fixture.detectChanges(); - let fileFake = {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'}; + let fileFake = new FileModel( {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'}); let filesList = [fileFake]; component.onFilesDropped(filesList); diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.ts index 08135078ce..b8b6974162 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.ts @@ -66,11 +66,6 @@ export class UploadDragAreaComponent { } } - ngOnChanges(changes) { - let formFields = this.createFormFields(); - this.uploadService.setOptions(formFields, this.versioning); - } - /** * Handles 'upload-files' events raised by child components. * @param e DOM event @@ -79,13 +74,14 @@ export class UploadDragAreaComponent { e.stopPropagation(); e.preventDefault(); - let files = e.detail.files; + let files: File[] = e.detail.files; if (files && files.length > 0) { + const fileModels = files.map(f => new FileModel(f, { newVersion: this.versioning })); if (e.detail.data.obj.entry.isFolder) { let id = e.detail.data.obj.entry.id; - this.onFilesDropped(files, id, '/'); + this.onFilesDropped(fileModels, id, '/'); } else { - this.onFilesDropped(files); + this.onFilesDropped(fileModels); } } } @@ -95,9 +91,9 @@ export class UploadDragAreaComponent { * * @param {File[]} files - files dropped in the drag area. */ - onFilesDropped(files: File[], rootId?: string, directory?: string): void { + onFilesDropped(files: FileModel[], rootId?: string, directory?: string): void { if (files.length) { - this.uploadService.addToQueue(files); + this.uploadService.addToQueue(...files); this.uploadService.uploadFilesInTheQueue(rootId || this.rootFolderId, directory || this.currentFolderPath, this.onSuccess); let latestFilesAdded = this.uploadService.getQueue(); if (this.showNotificationBar) { @@ -111,8 +107,9 @@ export class UploadDragAreaComponent { * @param item - FileEntity */ onFilesEntityDropped(item: any): void { - item.file((file: any) => { - this.uploadService.addToQueue([file]); + item.file((file: File) => { + const fileModel = new FileModel(file, { newVersion: this.versioning }); + this.uploadService.addToQueue(fileModel); let path = item.fullPath.replace(item.name, ''); let filePath = this.currentFolderPath + path; this.uploadService.uploadFilesInTheQueue(this.rootFolderId, filePath, this.onSuccess); @@ -228,12 +225,4 @@ export class UploadDragAreaComponent { } return message; } - - private createFormFields(): any { - return { - formFields: { - overwrite: true - } - }; - } } diff --git a/ng2-components/ng2-alfresco-upload/src/models/file.model.ts b/ng2-components/ng2-alfresco-upload/src/models/file.model.ts index 0909f82d33..eae4b8d054 100644 --- a/ng2-components/ng2-alfresco-upload/src/models/file.model.ts +++ b/ng2-components/ng2-alfresco-upload/src/models/file.model.ts @@ -34,14 +34,19 @@ export class FileModel { error: boolean = false; abort: boolean = false; uploading: boolean = false; - file: any; + file: File; promiseUpload: any; - constructor(file: any) { + options: FileUploadOptions; + + constructor(file: File, options?: FileUploadOptions) { this.file = file; - this.id = this._generateId(); + this.options = Object.assign({}, { + newVersion: false + }, options); + this.id = this.generateId(); this.name = file.name; - this.size = this._getFileSize(file.size); + this.size = this.getFileSize(file.size); this.progress = { loaded: 0, total: 0, @@ -115,7 +120,7 @@ export class FileModel { * * @param {number} sizeinbytes - size in bytes of the file. */ - private _getFileSize(sizeinbytes: number): string { + private getFileSize(sizeinbytes: number): string { let fSExt = new Array('Bytes', 'KB', 'MB', 'GB'); let size = sizeinbytes; let i = 0; @@ -126,15 +131,14 @@ export class FileModel { return Math.round((Math.round(size * 100) / 100)) + ' ' + fSExt[i]; } - /** - * Calculate the size of the file in kb,mb and gb. - * - * @return {string} - return a unique file uploading id. - */ - private _generateId(): string { + private generateId(): string { return 'uploading-file-' + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } } + +export interface FileUploadOptions { + newVersion?: boolean; +} diff --git a/ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts b/ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts index 6c92dedb80..086d536752 100644 --- a/ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts @@ -19,22 +19,13 @@ import { EventEmitter } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { CoreModule } from 'ng2-alfresco-core'; import { UploadService } from './upload.service'; +import { FileModel } from '../models/file.model'; declare let jasmine: any; describe('UploadService', () => { let service: UploadService; - let options = { - host: 'fakehost', - url: '/some/cool/url', - baseUrlPath: 'fakebasepath', - formFields: { - siteid: 'fakeSite', - containerid: 'fakeFolder' - } - }; - beforeEach(() => { TestBed.configureTestingModule({ imports: [ @@ -53,27 +44,32 @@ describe('UploadService', () => { }); it('should return an empty queue if no elements are added', () => { - service.setOptions(options, false); expect(service.getQueue().length).toEqual(0); }); it('should add an element in the queue and returns it', () => { - service.setOptions(options, false); - let filesFake = [{name: 'fake-name', size: 10}]; + let filesFake = new FileModel({name: 'fake-name', size: 10}); service.addToQueue(filesFake); expect(service.getQueue().length).toEqual(1); }); it('should add two elements in the queue and returns them', () => { - service.setOptions(options, false); let filesFake = [ - {name: 'fake-name', size: 10}, - {name: 'fake-name2', size: 20} + new FileModel({name: 'fake-name', size: 10}), + new FileModel({name: 'fake-name2', size: 20}) ]; - service.addToQueue(filesFake); + service.addToQueue(...filesFake); expect(service.getQueue().length).toEqual(2); }); + it('should skip hidden macOS files', () => { + const file1 = new FileModel(new File([''], '.git')); + const file2 = new FileModel(new File([''], 'readme.md')); + const result = service.addToQueue(file1, file2); + expect(result.length).toBe(1); + expect(result[0]).toBe(file2); + }); + it('should make XHR done request after the file is added in the queue', (done) => { let emitter = new EventEmitter(); @@ -81,9 +77,8 @@ describe('UploadService', () => { expect(e.value).toBe('File uploaded'); done(); }); - service.setOptions(options, false); - let filesFake = [{name: 'fake-name', size: 10}]; - service.addToQueue(filesFake); + let fileFake = new FileModel({name: 'fake-name', size: 10}); + service.addToQueue(fileFake); service.uploadFilesInTheQueue('-root-', 'fake-dir', emitter); let request = jasmine.Ajax.requests.mostRecent(); @@ -104,9 +99,8 @@ describe('UploadService', () => { expect(e.value).toBe('Error file uploaded'); done(); }); - service.setOptions(options, false); - let filesFake = [{name: 'fake-name', size: 10}]; - service.addToQueue(filesFake); + let fileFake = new FileModel({name: 'fake-name', size: 10}); + service.addToQueue(fileFake); service.uploadFilesInTheQueue('-root-', '', emitter); expect(jasmine.Ajax.requests.mostRecent().url) .toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true'); @@ -125,9 +119,8 @@ describe('UploadService', () => { expect(e.value).toEqual('File aborted'); done(); }); - service.setOptions(options, false); - let filesFake = [{name: 'fake-name', size: 10}]; - service.addToQueue(filesFake); + let fileFake = new FileModel({name: 'fake-name', size: 10}); + service.addToQueue(fileFake); service.uploadFilesInTheQueue('-root-', '', emitter); let file = service.getQueue(); @@ -141,9 +134,8 @@ describe('UploadService', () => { expect(e.value).toBe('Error file uploaded'); done(); }); - service.setOptions(options, false); - let filesFake = [{name: 'fake-name', size: 10}]; - service.addToQueue(filesFake); + let fileFake = new FileModel({name: 'fake-name', size: 10}); + service.addToQueue(fileFake); service.uploadFilesInTheQueue('-root-', '', emitter); let file = service.getQueue(); @@ -151,13 +143,12 @@ describe('UploadService', () => { }); it('should make XHR progress request after the onprogress is called', (done) => { - service.setOptions(options, false); let fakeProgress = { loaded: 500, total: 1234, percent: 44 }; - let filesFake = [{name: 'fake-name', size: 10}]; + let filesFake = new FileModel({name: 'fake-name', size: 10}); service.addToQueue(filesFake); service.filesUpload$.subscribe((file) => { expect(file).toBeDefined(); @@ -184,7 +175,6 @@ describe('UploadService', () => { resolve(fakeRest); }); spyOn(service, 'callApiCreateFolder').and.returnValue(fakePromise); - service.setOptions(options, false); let defaultPath = ''; let folderName = 'fake-folder'; service.createFolder(defaultPath, folderName).subscribe(res => { @@ -207,7 +197,6 @@ describe('UploadService', () => { reject(fakeRest); }); spyOn(service, 'callApiCreateFolder').and.returnValue(fakePromise); - service.setOptions(options, false); let defaultPath = ''; let folderName = 'folder-duplicate-fake'; service.createFolder(defaultPath, folderName).subscribe( @@ -223,9 +212,7 @@ describe('UploadService', () => { it('If versioning is true autoRename should not be present and majorVersion should be a param', () => { let emitter = new EventEmitter(); - let enableVersioning = true; - service.setOptions(options, enableVersioning); - let filesFake = [{name: 'fake-name', size: 10}]; + const filesFake = new FileModel({name: 'fake-name', size: 10}, { newVersion: true }); service.addToQueue(filesFake); service.uploadFilesInTheQueue('-root-', '', emitter); @@ -240,8 +227,7 @@ describe('UploadService', () => { expect(e.value).toBe('File uploaded'); done(); }); - service.setOptions(options, false); - let filesFake = [{name: 'fake-name', size: 10}]; + let filesFake = new FileModel({name: 'fake-name', size: 10}); service.addToQueue(filesFake); service.uploadFilesInTheQueue('123', 'fake-dir', emitter); diff --git a/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts b/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts index 2bf3a8b8ac..27c2fde967 100644 --- a/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts +++ b/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts @@ -31,9 +31,7 @@ import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; @Injectable() export class UploadService { - private formFields: Object = {}; private queue: FileModel[] = []; - private versioning: boolean = false; private filesUploadObserverProgressBar: Observer; private totalCompletedObserver: Observer; @@ -47,60 +45,45 @@ export class UploadService { this.totalCompleted$ = new Observable(observer => this.totalCompletedObserver = observer).share(); } - /** - * Configure the service - * - * @param {Object} - options formFields to init the object - * @param {boolean} - versioning true to indicate that a major version should be created - * - */ - setOptions(options: any, versioning: boolean): void { - this.formFields = options.formFields != null ? options.formFields : this.formFields; - this.versioning = versioning != null ? versioning : this.versioning; - } - /** * Add files to the uploading queue to be uploaded. * - * @param {File[]} - files to add to the upload queue. - * - * return {FileModel[]} - return the file added to the queue in this call. + * Examples: + * addToQueue(file); // pass one file + * addToQueue(file1, file2, file3); // pass multiple files + * addToQueue(...[file1, file2, file3]); // pass an array of files */ - addToQueue(files: File[]): FileModel[] { - const result: FileModel[] = []; - - for (let file of files) { - let uploadingFileModel = new FileModel(file); - result.push(uploadingFileModel); - this.queue.push(uploadingFileModel); - if (this.filesUploadObserverProgressBar) { - this.filesUploadObserverProgressBar.next(this.queue); - } + addToQueue(...files: FileModel[]): FileModel[] { + const allowedFiles = files.filter(f => !f.name.startsWith('.')); + this.queue = this.queue.concat(allowedFiles); + if (this.filesUploadObserverProgressBar) { + this.filesUploadObserverProgressBar.next(this.queue); } - return result; + return allowedFiles; } /** * Pick all the files in the queue that are not been uploaded yet and upload it into the directory folder. */ uploadFilesInTheQueue(rootId: string, directory: string, elementEmit: EventEmitter): void { - let filesToUpload = this.queue.filter((uploadingFileModel) => { - return !uploadingFileModel.uploading && !uploadingFileModel.done && !uploadingFileModel.abort && !uploadingFileModel.error; + let filesToUpload = this.queue.filter((file) => { + return !file.uploading && !file.done && !file.abort && !file.error; }); - let opts: any = {}; - opts.renditions = 'doclib'; - - if (this.versioning) { - opts.overwrite = true; - opts.majorVersion = true; - } else { - opts.autoRename = true; - } - filesToUpload.forEach((uploadingFileModel: FileModel) => { uploadingFileModel.setUploading(); + const opts: any = { + renditions: 'doclib' + }; + + if (uploadingFileModel.options.newVersion === true) { + opts.overwrite = true; + opts.majorVersion = true; + } else { + opts.autoRename = true; + } + let promiseUpload = this.apiService.getInstance().upload.uploadFile(uploadingFileModel.file, directory, rootId, null, opts) .on('progress', (progress: any) => { uploadingFileModel.setProgres(progress);