From 941028999efc819c9abde3812b5ba48dd29a3a76 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Thu, 14 Oct 2021 15:06:01 +0100 Subject: [PATCH] [MNT-22641] Upload versioning improvements (#7300) * support "versioningEnabled" for uploads * documentation updates * remove app config and extend the api docs --- docs/core/services/upload.service.md | 11 +- lib/core/models/file.model.ts | 38 +++++ lib/core/services/upload.service.spec.ts | 197 +++++++++++++++-------- lib/core/services/upload.service.ts | 9 +- 4 files changed, 189 insertions(+), 66 deletions(-) diff --git a/docs/core/services/upload.service.md b/docs/core/services/upload.service.md index e8bec5b281..2cd79ed2a5 100644 --- a/docs/core/services/upload.service.md +++ b/docs/core/services/upload.service.md @@ -89,7 +89,7 @@ node module. } ``` -From vesion 3.8.0 It's possible filter also for the folder whilst uploading a whole folder. +From version `3.8.0` it is also possible to filter out the folders: **app.config.json** @@ -110,4 +110,11 @@ From vesion 3.8.0 It's possible filter also for the folder whilst uploading a w ``` In this way all the files present in the .git folder won't be uploaded. -Please note that the filtering options available for the folders is the same as the one for the files. +> Please note that the filtering options available for the folders is the same as the one for the files. + +### Toggling Versioning Support + +It is also possible to provide the `versioningEnabled` value as part of the `FileUploadOptions` when using upload service from the code. + +> Note: When creating a new node using multipart/form-data by default versioning is enabled and set to MAJOR Version. +> Since Alfresco 6.2.3 versioningEnabled flag was introduced offering better control over the new node Versioning. diff --git a/lib/core/models/file.model.ts b/lib/core/models/file.model.ts index d218a9bdad..32c06d6588 100644 --- a/lib/core/models/file.model.ts +++ b/lib/core/models/file.model.ts @@ -24,16 +24,54 @@ export interface FileUploadProgress { } export class FileUploadOptions { + /** + * Add a version comment which will appear in version history. + * Setting this parameter also enables versioning of this node, if it is not already versioned. + */ comment?: string; + /** + * Overwrite the content of the node with a new version. + */ newVersion?: boolean; + /** + * If true, then created node will be version 1.0 MAJOR. If false, then created node will be version 0.1 MINOR. + */ majorVersion?: boolean; + /** + * Root folder id. + */ parentId?: string; + /** + * Defines the **relativePath** value. + * The relativePath specifies the folder structure to create relative to the node nodeId. + * Folders in the relativePath that do not exist are created before the node is created. + */ path?: string; + /** + * You can use the nodeType field to create a specific type. The default is **cm:content**. + */ nodeType?: string; + /** + * You can set multi-value properties when you create a new node which supports properties of type multiple. + */ properties?: any; + /** + * If the content model allows then it is also possible to create primary children with a different assoc type. + */ association?: any; + /** + * You can optionally specify an array of **secondaryChildren** to create one or more secondary child associations, + * such that the newly created node acts as a parent node. + */ secondaryChildren?: AssocChildBody[]; + /** + * You can optionally specify an array of **targets** to create one or more peer associations such that the newly created node acts as a source node. + */ targets?: AssociationBody[]; + /** + * If true, then created node will be versioned. If false, then created node will be unversioned and auto-versioning disabled. + */ + versioningEnabled?: boolean; } export enum FileUploadStatus { diff --git a/lib/core/services/upload.service.spec.ts b/lib/core/services/upload.service.spec.ts index 365f21d898..e2b4bcbec7 100644 --- a/lib/core/services/upload.service.spec.ts +++ b/lib/core/services/upload.service.spec.ts @@ -34,6 +34,9 @@ declare let jasmine: any; describe('UploadService', () => { let service: UploadService; + let appConfigService: AppConfigService; + let uploadFileSpy: jasmine.Spy; + const mockProductInfo = new BehaviorSubject(null); setupTestBed({ @@ -53,8 +56,8 @@ describe('UploadService', () => { }); beforeEach(() => { - const appConfig: AppConfigService = TestBed.inject(AppConfigService); - appConfig.config = { + appConfigService = TestBed.inject(AppConfigService); + appConfigService.config = { ecmHost: 'http://localhost:9876/ecm', files: { excluded: ['.DS_Store', 'desktop.ini', '.git', '*.git', '*.SWF'], @@ -75,6 +78,9 @@ describe('UploadService', () => { service = TestBed.inject(UploadService); service.queue = []; service.activeTask = null; + + uploadFileSpy = spyOn(service.uploadApi, 'uploadFile').and.callThrough(); + jasmine.Ajax.install(); mockProductInfo.next({ status: { isThumbnailGenerationEnabled: true } } as EcmProductVersionModel); }); @@ -203,7 +209,7 @@ describe('UploadService', () => { }); }); - it('should abort file only if it\'s safe to abort', (done) => { + it('should abort file only if it is safe to abort', (done) => { const emitter = new EventEmitter(); const emitterDisposable = emitter.subscribe((event) => { @@ -220,7 +226,7 @@ describe('UploadService', () => { service.cancelUpload(...file); }); - it('should let file complete and then delete node if it\'s not safe to abort', (done) => { + it('should let file complete and then delete node if it is not safe to abort', (done) => { const emitter = new EventEmitter(); const emitterDisposable = emitter.subscribe((event) => { @@ -261,7 +267,7 @@ describe('UploadService', () => { }); }); - it('should delete node\'s version when cancelling the upload of the new file version', (done) => { + it('should delete node version when cancelling the upload of the new file version', (done) => { const emitter = new EventEmitter(); const emitterDisposable = emitter.subscribe((event) => { @@ -306,27 +312,31 @@ describe('UploadService', () => { }); it('If newVersion is set, name should be a param', () => { - const uploadFileSpy = spyOn(service['uploadApi'], 'uploadFile').and.callThrough(); - const emitter = new EventEmitter(); - - const filesFake = new FileModel( { name: 'fake-name', size: 10 }, { - newVersion: true - }); + const filesFake = new FileModel( + { name: 'fake-name', size: 10 }, + { newVersion: true } + ); service.addToQueue(filesFake); service.uploadFilesInTheQueue(emitter); - expect(uploadFileSpy).toHaveBeenCalledWith({ - name: 'fake-name', - size: 10 - }, undefined, undefined, { newVersion: true }, { - renditions: 'doclib', - include: ['allowableOperations'], - overwrite: true, - majorVersion: undefined, - comment: undefined, - name: 'fake-name' - }); + expect(uploadFileSpy).toHaveBeenCalledWith( + { + name: 'fake-name', + size: 10 + }, + undefined, + undefined, + { newVersion: true }, + { + renditions: 'doclib', + include: ['allowableOperations'], + overwrite: true, + majorVersion: undefined, + comment: undefined, + name: 'fake-name' + } + ); }); it('should use custom root folder ID given to the service', (done) => { @@ -355,41 +365,98 @@ describe('UploadService', () => { }); }); - it('should append to the request the extra upload options', () => { - const uploadFileSpy = spyOn(service['uploadApi'], 'uploadFile').and.callThrough(); - const emitter = new EventEmitter(); + describe('versioningEnabled', () => { + it('should upload with "versioningEnabled" parameter taken from file options', () => { + const model = new FileModel( + { name: 'file-name', size: 10 }, + { + versioningEnabled: true + } + ); + service.addToQueue(model); + service.uploadFilesInTheQueue(); + + expect(uploadFileSpy).toHaveBeenCalledWith( + { + name: 'file-name', + size: 10 + }, + undefined, + undefined, + { newVersion: false }, + { + include: [ 'allowableOperations' ], + renditions: 'doclib', + versioningEnabled: true, + autoRename: true + } + ); + }); + + it('should not use "versioningEnabled" if not explicitly provided', () => { + const model = new FileModel( + { name: 'file-name', size: 10 }, + {} + ); + + service.addToQueue(model); + service.uploadFilesInTheQueue(); + + expect(uploadFileSpy).toHaveBeenCalledWith( + { + name: 'file-name', + size: 10 + }, + undefined, + undefined, + { newVersion: false }, + { + include: [ 'allowableOperations' ], + renditions: 'doclib', + autoRename: true + } + ); + }); + }); + + it('should append the extra upload options to the request', () => { const filesFake = new FileModel( { name: 'fake-name', size: 10 }, { - parentId: '123', path: 'fake-dir', + parentId: '123', + path: 'fake-dir', secondaryChildren: [ { assocType: 'assoc-1', childId: 'child-id' }], association: { assocType: 'fake-assoc' }, targets: [ { assocType: 'target-assoc', targetId: 'fake-target-id' }] }); service.addToQueue(filesFake); - service.uploadFilesInTheQueue(emitter); + service.uploadFilesInTheQueue(); - expect(uploadFileSpy).toHaveBeenCalledWith({ - name: 'fake-name', - size: 10 - }, 'fake-dir', '123', { - newVersion: false, - parentId: '123', - path: 'fake-dir', - secondaryChildren: [ { assocType: 'assoc-1', childId: 'child-id' }], - association: { assocType: 'fake-assoc' }, - targets: [ { assocType: 'target-assoc', targetId: 'fake-target-id' }] - }, { - renditions: 'doclib', - include: ['allowableOperations'], - autoRename: true - }); + expect(uploadFileSpy).toHaveBeenCalledWith( + { + name: 'fake-name', + size: 10 + }, + 'fake-dir', + '123', + { + newVersion: false, + parentId: '123', + path: 'fake-dir', + secondaryChildren: [ { assocType: 'assoc-1', childId: 'child-id' }], + association: { assocType: 'fake-assoc' }, + targets: [ { assocType: 'target-assoc', targetId: 'fake-target-id' }] + }, + { + renditions: 'doclib', + include: ['allowableOperations'], + autoRename: true + } + ); }); it('should start downloading the next one if a file of the list is aborted', (done) => { - const emitter = new EventEmitter(); - service.fileUploadAborted.subscribe((e) => { expect(e).not.toBeNull(); }); @@ -403,7 +470,7 @@ describe('UploadService', () => { const fileFake2 = new FileModel( { name: 'fake-name2', size: 10 }); const fileList = [fileFake1, fileFake2]; service.addToQueue(...fileList); - service.uploadFilesInTheQueue(emitter); + service.uploadFilesInTheQueue(); const file = service.getQueue(); service.cancelUpload(...file); @@ -445,7 +512,7 @@ describe('UploadService', () => { }); it('should call onUploadDeleted if file was deleted', () => { - const file = ({ status: FileUploadStatus.Deleted }); + const file = { status: FileUploadStatus.Deleted }; spyOn(service.fileUploadDeleted, 'next'); service.cancelUpload(file); @@ -474,24 +541,28 @@ describe('UploadService', () => { it('Should not pass rendition if it is disabled', () => { mockProductInfo.next({ status: { isThumbnailGenerationEnabled: false } } as EcmProductVersionModel); - const uploadFileSpy = spyOn(service['uploadApi'], 'uploadFile').and.callThrough(); - const emitter = new EventEmitter(); - - const filesFake = new FileModel( { name: 'fake-name', size: 10 }, { - newVersion: true - }); + const filesFake = new FileModel( + { name: 'fake-name', size: 10 }, + { newVersion: true} + ); service.addToQueue(filesFake); - service.uploadFilesInTheQueue(emitter); + service.uploadFilesInTheQueue(); - expect(uploadFileSpy).toHaveBeenCalledWith({ - name: 'fake-name', - size: 10 - }, undefined, undefined, { newVersion: true }, { - include: ['allowableOperations'], - overwrite: true, - majorVersion: undefined, - comment: undefined, - name: 'fake-name' - }); + expect(uploadFileSpy).toHaveBeenCalledWith( + { + name: 'fake-name', + size: 10 + }, + undefined, + undefined, + { newVersion: true }, + { + include: ['allowableOperations'], + overwrite: true, + majorVersion: undefined, + comment: undefined, + name: 'fake-name' + } + ); }); }); diff --git a/lib/core/services/upload.service.ts b/lib/core/services/upload.service.ts index 699c974c21..bad306078b 100644 --- a/lib/core/services/upload.service.ts +++ b/lib/core/services/upload.service.ts @@ -246,6 +246,10 @@ export class UploadService { opts.renditions = 'doclib'; } + if (file.options && file.options.versioningEnabled !== undefined) { + opts.versioningEnabled = file.options.versioningEnabled; + } + if (file.options.newVersion === true) { opts.overwrite = true; opts.majorVersion = file.options.majorVersion; @@ -262,11 +266,14 @@ export class UploadService { if (file.id) { return this.nodesApi.updateNodeContent(file.id, file.file, opts); } else { + const nodeBody = { ... file.options }; + delete nodeBody['versioningEnabled']; + return this.uploadApi.uploadFile( file.file, file.options.path, file.options.parentId, - file.options, + nodeBody, opts ); }