[ACA-1894] - added the possibility to filter whenever is possible by … (#5633)

* [ACA-1894] - added the possibility to filter whenever is possible by file path

* [ACA-1894] - fixed linting

* [ACA-1894] - reverted dynamic app config service refresh

* [ACA-1894] - added extra example in upload docs

* Update upload.service.md

Co-authored-by: Eugenio Romano <eromano@users.noreply.github.com>
This commit is contained in:
Vito 2020-04-24 14:25:49 +01:00 committed by GitHub
parent b9842ba12b
commit 6fea3b8cdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 251 additions and 43 deletions

View File

@ -463,6 +463,14 @@
"nocase": true "nocase": true
} }
}, },
"folders": {
"excluded": [
".git"
],
"match-options": {
"nocase": true
}
},
"logLevel": "trace", "logLevel": "trace",
"activiti": { "activiti": {
"rest": { "rest": {

View File

@ -87,6 +87,25 @@ node module.
} }
} }
``` ```
From vesion 3.8.0 It's possible filter also for the folder whilst uploading a whole folder.
Note that all standard glob patterns work and you can end patterns with a forward **app.config.json**
slash `/` character to specify a directory.
```json
{
"ecmHost": "http://localhost:3000/ecm",
"bpmHost": "http://localhost:3000/bpm",
"application": {
"name": "Alfresco"
},
"folders": {
"excluded": [".git"],
"match-options": {
"nocase": true
}
}
}
```
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.

View File

@ -704,6 +704,102 @@
} }
} }
}, },
"folders": {
"description": "Configuration of rules applied to file upload",
"type": "object",
"properties": {
"excluded": {
"description": "File exclusions",
"type": "array",
"items": {
"type": "string"
}
},
"match-options": {
"description": "Minimatch plugin option that will be applied for the check. By default all the options are false",
"type": "object",
"properties": {
"debug": {
"description": "Dump a ton of stuff to stderr",
"type": [
"boolean",
"null"
]
},
"nobrace": {
"description": "Do not expand {a,b} and {1..3} brace sets.",
"type": [
"boolean",
"null"
]
},
"noglobstar": {
"description": "Disable ** matching against multiple folder names.",
"type": [
"boolean",
"null"
]
},
"dot": {
"description": "Allow patterns to match filenames starting with a period, even if the pattern does not explicitly have a period in that spot.",
"type": [
"boolean",
"null"
]
},
"noext": {
"description": "Disable 'extglob' style patterns like +(a|b).",
"type": [
"boolean",
"null"
]
},
"nocase": {
"description": "Perform a case-insensitive match.",
"type": [
"boolean",
"null"
]
},
"nonull": {
"description": "When a match is not found by minimatch.match, return a list containing the pattern itself if this option is set. When not set, an empty list is returned if there are no matches.",
"type": [
"boolean",
"null"
]
},
"matchBase": {
"description": "If set, then patterns without slashes will be matched against the basename of the path if it contains slashes.",
"type": [
"boolean",
"null"
]
},
"nocomment": {
"description": "Suppress the behavior of treating # at the start of a pattern as a comment.",
"type": [
"boolean",
"null"
]
},
"nonegate": {
"description": "Suppress the behavior of treating a leading ! character as negation.",
"type": [
"boolean",
"null"
]
},
"flipNegate": {
"description": "Returns from negate expressions the same as if they were not negated.",
"type": [
"boolean",
"null"
]
}
}
}
}
},
"logLevel": { "logLevel": {
"description": "Application's logging level", "description": "Application's logging level",
"type": "string", "type": "string",

View File

@ -47,6 +47,13 @@ describe('UploadService', () => {
/* cspell:disable-next-line */ /* cspell:disable-next-line */
nocase: true nocase: true
} }
},
folders: {
excluded: ['ROLLINGPANDA'],
'match-options': {
/* cspell:disable-next-line */
nocase: true
}
} }
}; };
@ -319,6 +326,22 @@ describe('UploadService', () => {
expect(result[0]).toBe(file4); expect(result[0]).toBe(file4);
}); });
it('should skip files if they are in an excluded folder', () => {
const file1: any = { name: 'readmetoo.md', file : { webkitRelativePath: '/rollingPanda/' }};
const file2: any = { name: 'readme.md', file : { webkitRelativePath: '/test/' }};
const result = service.addToQueue(file1, file2);
expect(result.length).toBe(1);
expect(result[0]).toBe(file2);
});
it('should match the folder in case insensitive way', () => {
const file1: any = { name: 'readmetoo.md', file : { webkitRelativePath: '/rollingPanda/' }};
const file2: any = { name: 'readme.md', file : { webkitRelativePath: '/test/' }};
const result = service.addToQueue(file1, file2);
expect(result.length).toBe(1);
expect(result[0]).toBe(file2);
});
it('should call onUploadDeleted if file was deleted', async(() => { it('should call onUploadDeleted if file was deleted', async(() => {
const file = <any> ({ status: FileUploadStatus.Deleted }); const file = <any> ({ status: FileUploadStatus.Deleted });
spyOn(service.fileUploadDeleted, 'next'); spyOn(service.fileUploadDeleted, 'next');

View File

@ -35,13 +35,14 @@ const MAX_CANCELLABLE_FILE_PERCENTAGE = 50;
providedIn: 'root' providedIn: 'root'
}) })
export class UploadService { export class UploadService {
private cache: { [key: string]: any } = {}; private cache: { [key: string]: any } = {};
private totalComplete: number = 0; private totalComplete: number = 0;
private totalAborted: number = 0; private totalAborted: number = 0;
private totalError: number = 0; private totalError: number = 0;
private excludedFileList: string[] = []; private excludedFileList: string[] = [];
private excludedFoldersList: string[] = [];
private matchingOptions: any = null; private matchingOptions: any = null;
private folderMatchingOptions: any = null;
private abortedFile: string; private abortedFile: string;
activeTask: Promise<any> = null; activeTask: Promise<any> = null;
@ -49,16 +50,31 @@ export class UploadService {
queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>(); queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>();
fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>(); fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadStarting: Subject<FileUploadEvent> = new Subject<FileUploadEvent>(); fileUploadStarting: Subject<FileUploadEvent> = new Subject<
fileUploadCancelled: Subject<FileUploadEvent> = new Subject<FileUploadEvent>(); FileUploadEvent
fileUploadProgress: Subject<FileUploadEvent> = new Subject<FileUploadEvent>(); >();
fileUploadAborted: Subject<FileUploadEvent> = new Subject<FileUploadEvent>(); fileUploadCancelled: Subject<FileUploadEvent> = new Subject<
fileUploadError: Subject<FileUploadErrorEvent> = new Subject<FileUploadErrorEvent>(); FileUploadEvent
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<FileUploadCompleteEvent>(); >();
fileUploadDeleted: Subject<FileUploadDeleteEvent> = new Subject<FileUploadDeleteEvent>(); fileUploadProgress: Subject<FileUploadEvent> = new Subject<
FileUploadEvent
>();
fileUploadAborted: Subject<FileUploadEvent> = new Subject<
FileUploadEvent
>();
fileUploadError: Subject<FileUploadErrorEvent> = new Subject<
FileUploadErrorEvent
>();
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<
FileUploadCompleteEvent
>();
fileUploadDeleted: Subject<FileUploadDeleteEvent> = new Subject<
FileUploadDeleteEvent
>();
fileDeleted: Subject<string> = new Subject<string>(); fileDeleted: Subject<string> = new Subject<string>();
constructor(protected apiService: AlfrescoApiService, private appConfigService: AppConfigService) { constructor(protected apiService: AlfrescoApiService, private appConfigService: AppConfigService) {
} }
/** /**
@ -83,35 +99,66 @@ export class UploadService {
* @returns Array of files that were not blocked from upload by the ignore list * @returns Array of files that were not blocked from upload by the ignore list
*/ */
addToQueue(...files: FileModel[]): FileModel[] { addToQueue(...files: FileModel[]): FileModel[] {
const allowedFiles = files.filter((currentFile) => this.filterElement(currentFile)); const allowedFiles = files.filter((currentFile) =>
this.filterElement(currentFile)
);
this.queue = this.queue.concat(allowedFiles); this.queue = this.queue.concat(allowedFiles);
this.queueChanged.next(this.queue); this.queueChanged.next(this.queue);
return allowedFiles; return allowedFiles;
} }
private filterElement(file: FileModel) { private filterElement(file: FileModel) {
this.excludedFileList = <string[]> this.appConfigService.get('files.excluded');
this.excludedFoldersList = <string[]> this.appConfigService.get('folders.excluded');
let isAllowed = true; let isAllowed = true;
this.excludedFileList = <string[]> this.appConfigService.get('files.excluded');
if (this.excludedFileList) { if (this.excludedFileList) {
this.matchingOptions = this.appConfigService.get('files.match-options'); this.matchingOptions = this.appConfigService.get('files.match-options');
isAllowed = this.isFileNameAllowed(file);
}
isAllowed = this.excludedFileList.filter((pattern) => { if (isAllowed && this.excludedFoldersList) {
const minimatch = new Minimatch(pattern, this.matchingOptions); this.folderMatchingOptions = this.appConfigService.get('folders.match-options');
return minimatch.match(file.name); isAllowed = this.isParentFolderAllowed(file);
}
return isAllowed;
}
private isParentFolderAllowed(file: FileModel): boolean {
let isAllowed: boolean = true;
const currentFile: any = file.file;
if (currentFile && currentFile.webkitRelativePath) {
isAllowed =
this.excludedFoldersList.filter((folderToExclude) => {
return currentFile.webkitRelativePath
.split('/')
.some((pathElement) => {
const minimatch = new Minimatch(folderToExclude, this.folderMatchingOptions);
return minimatch.match(pathElement);
});
}).length === 0; }).length === 0;
} }
return isAllowed; return isAllowed;
} }
private isFileNameAllowed(file: FileModel): boolean {
return (
this.excludedFileList.filter((pattern) => {
const minimatch = new Minimatch(pattern, this.matchingOptions);
return minimatch.match(file.name);
}).length === 0
);
}
/** /**
* Finds all the files in the queue that are not yet uploaded and uploads them into the directory folder. * Finds all the files in the queue that are not yet uploaded and uploads them into the directory folder.
* @param emitter Emitter to invoke on file status change * @param emitter Emitter to invoke on file status change
*/ */
uploadFilesInTheQueue(emitter?: EventEmitter<any>): void { uploadFilesInTheQueue(emitter?: EventEmitter<any>): void {
if (!this.activeTask) { if (!this.activeTask) {
const file = this.queue.find((currentFile) => currentFile.status === FileUploadStatus.Pending); const file = this.queue.find(
(currentFile) => currentFile.status === FileUploadStatus.Pending
);
if (file) { if (file) {
this.onUploadStarting(file); this.onUploadStarting(file);
@ -150,7 +197,6 @@ export class UploadService {
this.abortedFile = file.name; this.abortedFile = file.name;
delete this.cache[file.name]; delete this.cache[file.name];
promise.next(); promise.next();
} else { } else {
const performAction = this.getAction(file); const performAction = this.getAction(file);
performAction(); performAction();
@ -191,13 +237,13 @@ export class UploadService {
} }
if (file.id) { if (file.id) {
return this.apiService.getInstance().node.updateNodeContent( return this.apiService
file.id, .getInstance()
file.file, .node.updateNodeContent(file.id, file.file, opts);
opts
);
} else { } else {
return this.apiService.getInstance().upload.uploadFile( return this.apiService
.getInstance()
.upload.uploadFile(
file.file, file.file,
file.options.path, file.options.path,
file.options.parentId, file.options.parentId,
@ -208,9 +254,9 @@ export class UploadService {
} }
private beginUpload(file: FileModel, emitter: EventEmitter<any>): any { private beginUpload(file: FileModel, emitter: EventEmitter<any>): any {
const promise = this.getUploadPromise(file); const promise = this.getUploadPromise(file);
promise.on('progress', (progress: FileUploadProgress) => { promise
.on('progress', (progress: FileUploadProgress) => {
this.onUploadProgress(file, progress); this.onUploadProgress(file, progress);
}) })
.on('abort', () => { .on('abort', () => {
@ -239,7 +285,7 @@ export class UploadService {
} }
} }
}) })
.catch(() => { }); .catch(() => {});
return promise; return promise;
} }
@ -253,7 +299,10 @@ export class UploadService {
} }
} }
private onUploadProgress(file: FileModel, progress: FileUploadProgress): void { private onUploadProgress(
file: FileModel,
progress: FileUploadProgress
): void {
if (file) { if (file) {
file.progress = progress; file.progress = progress;
file.status = FileUploadStatus.Progress; file.status = FileUploadStatus.Progress;
@ -275,7 +324,11 @@ export class UploadService {
delete this.cache[file.name]; delete this.cache[file.name];
} }
const event = new FileUploadErrorEvent(file, error, this.totalError); const event = new FileUploadErrorEvent(
file,
error,
this.totalError
);
this.fileUpload.next(event); this.fileUpload.next(event);
this.fileUploadError.next(event); this.fileUploadError.next(event);
} }
@ -291,7 +344,12 @@ export class UploadService {
delete this.cache[file.name]; delete this.cache[file.name];
} }
const event = new FileUploadCompleteEvent(file, this.totalComplete, data, this.totalAborted); const event = new FileUploadCompleteEvent(
file,
this.totalComplete,
data,
this.totalAborted
);
this.fileUpload.next(event); this.fileUpload.next(event);
this.fileUploadComplete.next(event); this.fileUploadComplete.next(event);
} }
@ -340,12 +398,16 @@ export class UploadService {
} }
private deleteAbortedNode(nodeId: string) { private deleteAbortedNode(nodeId: string) {
this.apiService.getInstance().core.nodesApi.deleteNode(nodeId, { permanent: true }) this.apiService
.then(() => this.abortedFile = undefined); .getInstance()
.core.nodesApi.deleteNode(nodeId, { permanent: true })
.then(() => (this.abortedFile = undefined));
} }
private isSaveToAbortFile(file: FileModel): boolean { private isSaveToAbortFile(file: FileModel): boolean {
return file.size > MIN_CANCELLABLE_FILE_SIZE && file.progress.percent < MAX_CANCELLABLE_FILE_PERCENTAGE; return (
file.size > MIN_CANCELLABLE_FILE_SIZE &&
file.progress.percent < MAX_CANCELLABLE_FILE_PERCENTAGE
);
} }
} }