[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
}
},
"folders": {
"excluded": [
".git"
],
"match-options": {
"nocase": true
}
},
"logLevel": "trace",
"activiti": {
"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
slash `/` character to specify a directory.
**app.config.json**
```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": {
"description": "Application's logging level",
"type": "string",

View File

@ -47,6 +47,13 @@ describe('UploadService', () => {
/* cspell:disable-next-line */
nocase: true
}
},
folders: {
excluded: ['ROLLINGPANDA'],
'match-options': {
/* cspell:disable-next-line */
nocase: true
}
}
};
@ -319,6 +326,22 @@ describe('UploadService', () => {
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(() => {
const file = <any> ({ status: FileUploadStatus.Deleted });
spyOn(service.fileUploadDeleted, 'next');

View File

@ -35,13 +35,14 @@ const MAX_CANCELLABLE_FILE_PERCENTAGE = 50;
providedIn: 'root'
})
export class UploadService {
private cache: { [key: string]: any } = {};
private totalComplete: number = 0;
private totalAborted: number = 0;
private totalError: number = 0;
private excludedFileList: string[] = [];
private excludedFoldersList: string[] = [];
private matchingOptions: any = null;
private folderMatchingOptions: any = null;
private abortedFile: string;
activeTask: Promise<any> = null;
@ -49,16 +50,31 @@ export class UploadService {
queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>();
fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadStarting: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadCancelled: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
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>();
fileUploadStarting: Subject<FileUploadEvent> = new Subject<
FileUploadEvent
>();
fileUploadCancelled: Subject<FileUploadEvent> = new Subject<
FileUploadEvent
>();
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>();
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
*/
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.queueChanged.next(this.queue);
return allowedFiles;
}
private filterElement(file: FileModel) {
this.excludedFileList = <string[]> this.appConfigService.get('files.excluded');
this.excludedFoldersList = <string[]> this.appConfigService.get('folders.excluded');
let isAllowed = true;
this.excludedFileList = <string[]> this.appConfigService.get('files.excluded');
if (this.excludedFileList) {
this.matchingOptions = this.appConfigService.get('files.match-options');
isAllowed = this.isFileNameAllowed(file);
}
isAllowed = this.excludedFileList.filter((pattern) => {
const minimatch = new Minimatch(pattern, this.matchingOptions);
return minimatch.match(file.name);
}).length === 0;
if (isAllowed && this.excludedFoldersList) {
this.folderMatchingOptions = this.appConfigService.get('folders.match-options');
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;
}
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.
* @param emitter Emitter to invoke on file status change
*/
uploadFilesInTheQueue(emitter?: EventEmitter<any>): void {
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) {
this.onUploadStarting(file);
@ -150,7 +197,6 @@ export class UploadService {
this.abortedFile = file.name;
delete this.cache[file.name];
promise.next();
} else {
const performAction = this.getAction(file);
performAction();
@ -191,28 +237,28 @@ export class UploadService {
}
if (file.id) {
return this.apiService.getInstance().node.updateNodeContent(
file.id,
file.file,
opts
);
return this.apiService
.getInstance()
.node.updateNodeContent(file.id, file.file, opts);
} else {
return this.apiService.getInstance().upload.uploadFile(
file.file,
file.options.path,
file.options.parentId,
file.options,
opts
);
return this.apiService
.getInstance()
.upload.uploadFile(
file.file,
file.options.path,
file.options.parentId,
file.options,
opts
);
}
}
private beginUpload(file: FileModel, emitter: EventEmitter<any>): any {
const promise = this.getUploadPromise(file);
promise.on('progress', (progress: FileUploadProgress) => {
this.onUploadProgress(file, progress);
})
promise
.on('progress', (progress: FileUploadProgress) => {
this.onUploadProgress(file, progress);
})
.on('abort', () => {
this.onUploadAborted(file);
if (emitter) {
@ -239,7 +285,7 @@ export class UploadService {
}
}
})
.catch(() => { });
.catch(() => {});
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) {
file.progress = progress;
file.status = FileUploadStatus.Progress;
@ -275,7 +324,11 @@ export class UploadService {
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.fileUploadError.next(event);
}
@ -291,7 +344,12 @@ export class UploadService {
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.fileUploadComplete.next(event);
}
@ -340,12 +398,16 @@ export class UploadService {
}
private deleteAbortedNode(nodeId: string) {
this.apiService.getInstance().core.nodesApi.deleteNode(nodeId, { permanent: true })
.then(() => this.abortedFile = undefined);
this.apiService
.getInstance()
.core.nodesApi.deleteNode(nodeId, { permanent: true })
.then(() => (this.abortedFile = undefined));
}
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
);
}
}