[MNT-22641] Upload versioning improvements (#7300)

* support "versioningEnabled" for uploads

* documentation updates

* remove app config and extend the api docs
This commit is contained in:
Denys Vuika
2021-10-14 15:06:01 +01:00
committed by GitHub
parent a3dc441703
commit 941028999e
4 changed files with 189 additions and 66 deletions

View File

@@ -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.

View File

@@ -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 {

View File

@@ -34,6 +34,9 @@ declare let jasmine: any;
describe('UploadService', () => {
let service: UploadService;
let appConfigService: AppConfigService;
let uploadFileSpy: jasmine.Spy;
const mockProductInfo = new BehaviorSubject<EcmProductVersionModel>(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(<File> { name: 'fake-name', size: 10 }, {
newVersion: true
});
const filesFake = new FileModel(
<File> { 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(
<File> { name: 'file-name', size: 10 },
<FileUploadOptions> {
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(
<File> { name: 'file-name', size: 10 },
<FileUploadOptions> {}
);
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(
<File> { name: 'fake-name', size: 10 },
<FileUploadOptions> {
parentId: '123', path: 'fake-dir',
parentId: '123',
path: 'fake-dir',
secondaryChildren: [<AssocChildBody> { assocType: 'assoc-1', childId: 'child-id' }],
association: { assocType: 'fake-assoc' },
targets: [<AssociationBody> { 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: [<AssocChildBody> { assocType: 'assoc-1', childId: 'child-id' }],
association: { assocType: 'fake-assoc' },
targets: [<AssociationBody> { 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: [<AssocChildBody> { assocType: 'assoc-1', childId: 'child-id' }],
association: { assocType: 'fake-assoc' },
targets: [<AssociationBody> { 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(<File> { 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 = <any> ({ status: FileUploadStatus.Deleted });
const file = <FileModel> { 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(<File> { name: 'fake-name', size: 10 }, {
newVersion: true
});
const filesFake = new FileModel(
<File> { 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'
}
);
});
});

View File

@@ -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, <any> 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
);
}