@arela/uploader 0.2.13 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +66 -0
- package/README.md +263 -62
- package/docs/API_ENDPOINTS_FOR_DETECTION.md +647 -0
- package/docs/QUICK_REFERENCE_API_DETECTION.md +264 -0
- package/docs/REFACTORING_SUMMARY_DETECT_PEDIMENTOS.md +200 -0
- package/package.json +3 -2
- package/scripts/cleanup-ds-store.js +109 -0
- package/scripts/cleanup-system-files.js +69 -0
- package/scripts/tests/phase-7-features.test.js +415 -0
- package/scripts/tests/signal-handling.test.js +275 -0
- package/scripts/tests/smart-watch-integration.test.js +554 -0
- package/scripts/tests/watch-service-integration.test.js +584 -0
- package/src/commands/UploadCommand.js +31 -4
- package/src/commands/WatchCommand.js +1342 -0
- package/src/config/config.js +270 -2
- package/src/document-type-shared.js +2 -0
- package/src/document-types/support-document.js +200 -0
- package/src/file-detection.js +9 -1
- package/src/index.js +163 -4
- package/src/services/AdvancedFilterService.js +505 -0
- package/src/services/AutoProcessingService.js +749 -0
- package/src/services/BenchmarkingService.js +381 -0
- package/src/services/DatabaseService.js +1019 -539
- package/src/services/ErrorMonitor.js +275 -0
- package/src/services/LoggingService.js +419 -1
- package/src/services/MonitoringService.js +401 -0
- package/src/services/PerformanceOptimizer.js +511 -0
- package/src/services/ReportingService.js +511 -0
- package/src/services/SignalHandler.js +255 -0
- package/src/services/SmartWatchDatabaseService.js +527 -0
- package/src/services/WatchService.js +783 -0
- package/src/services/upload/ApiUploadService.js +447 -3
- package/src/services/upload/MultiApiUploadService.js +233 -0
- package/src/services/upload/SupabaseUploadService.js +12 -5
- package/src/services/upload/UploadServiceFactory.js +24 -0
- package/src/utils/CleanupManager.js +262 -0
- package/src/utils/FileOperations.js +44 -0
- package/src/utils/WatchEventHandler.js +522 -0
- package/supabase/migrations/001_create_initial_schema.sql +366 -0
- package/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
- package/.envbackup +0 -37
- package/SUPABASE_UPLOAD_FIX.md +0 -157
- package/commands.md +0 -14
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdvancedFilterService.js
|
|
3
|
+
* Phase 7 - Task 1: Advanced Filtering & Validation
|
|
4
|
+
*
|
|
5
|
+
* Provides advanced filtering options for files including:
|
|
6
|
+
* - File type filtering
|
|
7
|
+
* - File size filtering
|
|
8
|
+
* - Date range filtering
|
|
9
|
+
* - Custom validation rules
|
|
10
|
+
* - Complex filter combinations
|
|
11
|
+
*/
|
|
12
|
+
import fs from 'fs/promises';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
|
|
15
|
+
class AdvancedFilterService {
|
|
16
|
+
constructor(logger) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
|
|
19
|
+
// Filter presets
|
|
20
|
+
this.filterPresets = {
|
|
21
|
+
pdfOnly: {
|
|
22
|
+
name: 'PDF Files Only',
|
|
23
|
+
filters: [{ type: 'extension', value: '.pdf', operator: 'equals' }],
|
|
24
|
+
},
|
|
25
|
+
largeFiles: {
|
|
26
|
+
name: 'Large Files (>10MB)',
|
|
27
|
+
filters: [
|
|
28
|
+
{ type: 'size', value: 10 * 1024 * 1024, operator: 'greaterThan' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
recentFiles: {
|
|
32
|
+
name: 'Recent Files (Last 7 days)',
|
|
33
|
+
filters: [{ type: 'dateModified', value: 7, operator: 'daysAgo' }],
|
|
34
|
+
},
|
|
35
|
+
documentsOnly: {
|
|
36
|
+
name: 'Documents',
|
|
37
|
+
filters: [
|
|
38
|
+
{
|
|
39
|
+
type: 'extension',
|
|
40
|
+
value: ['.pdf', '.doc', '.docx', '.xls', '.xlsx'],
|
|
41
|
+
operator: 'in',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Validation rules
|
|
48
|
+
this.validationRules = {
|
|
49
|
+
pdfs: {
|
|
50
|
+
extension: '.pdf',
|
|
51
|
+
minSize: 0,
|
|
52
|
+
maxSize: 500 * 1024 * 1024, // 500MB
|
|
53
|
+
allowedFormats: ['pdf'],
|
|
54
|
+
},
|
|
55
|
+
documents: {
|
|
56
|
+
extension: ['.pdf', '.doc', '.docx', '.xls', '.xlsx'],
|
|
57
|
+
minSize: 0,
|
|
58
|
+
maxSize: 100 * 1024 * 1024,
|
|
59
|
+
allowedFormats: ['pdf', 'doc', 'docx', 'xls', 'xlsx'],
|
|
60
|
+
},
|
|
61
|
+
images: {
|
|
62
|
+
extension: ['.jpg', '.jpeg', '.png', '.gif', '.bmp'],
|
|
63
|
+
minSize: 0,
|
|
64
|
+
maxSize: 50 * 1024 * 1024,
|
|
65
|
+
allowedFormats: ['jpg', 'jpeg', 'png', 'gif', 'bmp'],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Statistics
|
|
70
|
+
this.stats = {
|
|
71
|
+
filesFiltered: 0,
|
|
72
|
+
filesMatched: 0,
|
|
73
|
+
filesRejected: 0,
|
|
74
|
+
filterApplications: 0,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Apply multiple filters to a file list
|
|
80
|
+
*/
|
|
81
|
+
async filterFiles(files, filterConfig) {
|
|
82
|
+
const startTime = Date.now();
|
|
83
|
+
this.stats.filterApplications++;
|
|
84
|
+
|
|
85
|
+
let filtered = [...files];
|
|
86
|
+
const filterResults = {
|
|
87
|
+
original: files.length,
|
|
88
|
+
stages: [],
|
|
89
|
+
final: 0,
|
|
90
|
+
duration: 0,
|
|
91
|
+
rejectedFiles: [],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (!filterConfig || Object.keys(filterConfig).length === 0) {
|
|
95
|
+
return {
|
|
96
|
+
files: filtered,
|
|
97
|
+
results: filterResults,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Apply each filter in sequence
|
|
102
|
+
const filterEntries = Object.entries(filterConfig);
|
|
103
|
+
for (const [filterType, filterValue] of filterEntries) {
|
|
104
|
+
const beforeCount = filtered.length;
|
|
105
|
+
|
|
106
|
+
switch (filterType) {
|
|
107
|
+
case 'extension':
|
|
108
|
+
filtered = await this._filterByExtension(filtered, filterValue);
|
|
109
|
+
break;
|
|
110
|
+
case 'size':
|
|
111
|
+
filtered = await this._filterBySize(filtered, filterValue);
|
|
112
|
+
break;
|
|
113
|
+
case 'dateModified':
|
|
114
|
+
filtered = await this._filterByDateModified(filtered, filterValue);
|
|
115
|
+
break;
|
|
116
|
+
case 'dateCreated':
|
|
117
|
+
filtered = await this._filterByDateCreated(filtered, filterValue);
|
|
118
|
+
break;
|
|
119
|
+
case 'custom':
|
|
120
|
+
filtered = await this._filterByCustomRule(filtered, filterValue);
|
|
121
|
+
break;
|
|
122
|
+
case 'validation':
|
|
123
|
+
filtered = await this._filterByValidation(filtered, filterValue);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const afterCount = filtered.length;
|
|
128
|
+
filterResults.stages.push({
|
|
129
|
+
filterType,
|
|
130
|
+
before: beforeCount,
|
|
131
|
+
after: afterCount,
|
|
132
|
+
removed: beforeCount - afterCount,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
filterResults.final = filtered.length;
|
|
137
|
+
filterResults.duration = Date.now() - startTime;
|
|
138
|
+
|
|
139
|
+
this.stats.filesFiltered += files.length;
|
|
140
|
+
this.stats.filesMatched += filtered.length;
|
|
141
|
+
this.stats.filesRejected += files.length - filtered.length;
|
|
142
|
+
|
|
143
|
+
this.logger.debug(
|
|
144
|
+
`Filtered ${files.length} files to ${filtered.length} (${filterResults.duration}ms)`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
files: filtered,
|
|
149
|
+
results: filterResults,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Filter by file extension
|
|
155
|
+
*/
|
|
156
|
+
async _filterByExtension(files, extensions) {
|
|
157
|
+
const extensionList = Array.isArray(extensions) ? extensions : [extensions];
|
|
158
|
+
const normalized = extensionList.map((ext) => ext.toLowerCase());
|
|
159
|
+
|
|
160
|
+
return files.filter((file) => {
|
|
161
|
+
const fileExt = path.extname(file).toLowerCase();
|
|
162
|
+
return normalized.includes(fileExt);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Filter by file size
|
|
168
|
+
*/
|
|
169
|
+
async _filterBySize(files, sizeConfig) {
|
|
170
|
+
const filtered = [];
|
|
171
|
+
|
|
172
|
+
for (const file of files) {
|
|
173
|
+
try {
|
|
174
|
+
const stats = await fs.stat(file);
|
|
175
|
+
|
|
176
|
+
if (sizeConfig.min && stats.size < sizeConfig.min) continue;
|
|
177
|
+
if (sizeConfig.max && stats.size > sizeConfig.max) continue;
|
|
178
|
+
|
|
179
|
+
filtered.push(file);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.logger.warn(`Failed to stat file: ${file}`, error.message);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return filtered;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Filter by date modified
|
|
190
|
+
*/
|
|
191
|
+
async _filterByDateModified(files, dateConfig) {
|
|
192
|
+
const filtered = [];
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
|
|
195
|
+
for (const file of files) {
|
|
196
|
+
try {
|
|
197
|
+
const stats = await fs.stat(file);
|
|
198
|
+
const fileTime = stats.mtime.getTime();
|
|
199
|
+
|
|
200
|
+
if (dateConfig.daysAgo !== undefined) {
|
|
201
|
+
const daysInMs = dateConfig.daysAgo * 24 * 60 * 60 * 1000;
|
|
202
|
+
if (now - fileTime > daysInMs) continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
dateConfig.before &&
|
|
207
|
+
fileTime > new Date(dateConfig.before).getTime()
|
|
208
|
+
)
|
|
209
|
+
continue;
|
|
210
|
+
if (dateConfig.after && fileTime < new Date(dateConfig.after).getTime())
|
|
211
|
+
continue;
|
|
212
|
+
|
|
213
|
+
filtered.push(file);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
this.logger.warn(`Failed to stat file: ${file}`, error.message);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return filtered;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Filter by date created
|
|
224
|
+
*/
|
|
225
|
+
async _filterByDateCreated(files, dateConfig) {
|
|
226
|
+
const filtered = [];
|
|
227
|
+
|
|
228
|
+
for (const file of files) {
|
|
229
|
+
try {
|
|
230
|
+
const stats = await fs.stat(file);
|
|
231
|
+
const birthTime = stats.birthtime.getTime();
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
|
|
234
|
+
if (dateConfig.daysAgo !== undefined) {
|
|
235
|
+
const daysInMs = dateConfig.daysAgo * 24 * 60 * 60 * 1000;
|
|
236
|
+
if (now - birthTime > daysInMs) continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
filtered.push(file);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
this.logger.warn(`Failed to stat file: ${file}`, error.message);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return filtered;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Filter by custom validation rule
|
|
250
|
+
*/
|
|
251
|
+
async _filterByCustomRule(files, ruleFunction) {
|
|
252
|
+
const filtered = [];
|
|
253
|
+
|
|
254
|
+
for (const file of files) {
|
|
255
|
+
try {
|
|
256
|
+
const stats = await fs.stat(file);
|
|
257
|
+
const shouldInclude = await ruleFunction({
|
|
258
|
+
path: file,
|
|
259
|
+
name: path.basename(file),
|
|
260
|
+
size: stats.size,
|
|
261
|
+
modified: stats.mtime,
|
|
262
|
+
created: stats.birthtime,
|
|
263
|
+
isDirectory: stats.isDirectory(),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (shouldInclude) {
|
|
267
|
+
filtered.push(file);
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
this.logger.warn(`Custom filter error for ${file}:`, error.message);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return filtered;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Filter by validation rule preset
|
|
279
|
+
*/
|
|
280
|
+
async _filterByValidation(files, validationRule) {
|
|
281
|
+
const rule =
|
|
282
|
+
typeof validationRule === 'string'
|
|
283
|
+
? this.validationRules[validationRule]
|
|
284
|
+
: validationRule;
|
|
285
|
+
|
|
286
|
+
if (!rule) {
|
|
287
|
+
this.logger.warn(`Unknown validation rule: ${validationRule}`);
|
|
288
|
+
return files;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const filtered = [];
|
|
292
|
+
|
|
293
|
+
for (const file of files) {
|
|
294
|
+
try {
|
|
295
|
+
const ext = path.extname(file).toLowerCase();
|
|
296
|
+
const stats = await fs.stat(file);
|
|
297
|
+
|
|
298
|
+
// Check extension
|
|
299
|
+
const allowedExts = Array.isArray(rule.extension)
|
|
300
|
+
? rule.extension
|
|
301
|
+
: [rule.extension];
|
|
302
|
+
if (!allowedExts.includes(ext)) continue;
|
|
303
|
+
|
|
304
|
+
// Check size
|
|
305
|
+
if (stats.size < rule.minSize) continue;
|
|
306
|
+
if (stats.size > rule.maxSize) continue;
|
|
307
|
+
|
|
308
|
+
filtered.push(file);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
this.logger.warn(`Validation error for ${file}:`, error.message);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return filtered;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Create a complex filter query
|
|
319
|
+
*/
|
|
320
|
+
createFilterQuery(config) {
|
|
321
|
+
const query = {
|
|
322
|
+
filters: [],
|
|
323
|
+
logic: config.logic || 'AND',
|
|
324
|
+
description: config.description || 'Custom filter query',
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
if (config.extensions) {
|
|
328
|
+
query.filters.push({
|
|
329
|
+
type: 'extension',
|
|
330
|
+
operator: 'in',
|
|
331
|
+
values: Array.isArray(config.extensions)
|
|
332
|
+
? config.extensions
|
|
333
|
+
: [config.extensions],
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (config.minSize !== undefined) {
|
|
338
|
+
query.filters.push({
|
|
339
|
+
type: 'size',
|
|
340
|
+
operator: 'greaterThan',
|
|
341
|
+
value: config.minSize,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (config.maxSize !== undefined) {
|
|
346
|
+
query.filters.push({
|
|
347
|
+
type: 'size',
|
|
348
|
+
operator: 'lessThan',
|
|
349
|
+
value: config.maxSize,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (config.modifiedInDays !== undefined) {
|
|
354
|
+
query.filters.push({
|
|
355
|
+
type: 'dateModified',
|
|
356
|
+
operator: 'daysAgo',
|
|
357
|
+
value: config.modifiedInDays,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return query;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Apply a filter preset
|
|
366
|
+
*/
|
|
367
|
+
async applyPreset(files, presetName) {
|
|
368
|
+
const preset = this.filterPresets[presetName];
|
|
369
|
+
|
|
370
|
+
if (!preset) {
|
|
371
|
+
this.logger.error(`Unknown preset: ${presetName}`);
|
|
372
|
+
return { files, results: { error: `Unknown preset: ${presetName}` } };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const filterConfig = {};
|
|
376
|
+
|
|
377
|
+
for (const filter of preset.filters) {
|
|
378
|
+
if (filter.type === 'extension') {
|
|
379
|
+
filterConfig.extension = filter.value;
|
|
380
|
+
} else if (filter.type === 'size') {
|
|
381
|
+
filterConfig.size = {
|
|
382
|
+
[filter.operator === 'greaterThan' ? 'min' : 'max']: filter.value,
|
|
383
|
+
};
|
|
384
|
+
} else if (filter.type === 'dateModified') {
|
|
385
|
+
filterConfig.dateModified = { daysAgo: filter.value };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return this.filterFiles(files, filterConfig);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Get available filter presets
|
|
394
|
+
*/
|
|
395
|
+
getFilterPresets() {
|
|
396
|
+
return Object.entries(this.filterPresets).map(([key, preset]) => ({
|
|
397
|
+
key,
|
|
398
|
+
name: preset.name,
|
|
399
|
+
description: `Filter: ${preset.filters.map((f) => f.type).join(', ')}`,
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get available validation rules
|
|
405
|
+
*/
|
|
406
|
+
getValidationRules() {
|
|
407
|
+
return Object.entries(this.validationRules).map(([key, rule]) => ({
|
|
408
|
+
key,
|
|
409
|
+
extensions: Array.isArray(rule.extension)
|
|
410
|
+
? rule.extension
|
|
411
|
+
: [rule.extension],
|
|
412
|
+
minSize: rule.minSize,
|
|
413
|
+
maxSize: rule.maxSize,
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Validate a single file against rule
|
|
419
|
+
*/
|
|
420
|
+
async validateFile(filePath, ruleName) {
|
|
421
|
+
const rule = this.validationRules[ruleName];
|
|
422
|
+
|
|
423
|
+
if (!rule) {
|
|
424
|
+
return { valid: false, error: `Unknown rule: ${ruleName}` };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const stats = await fs.stat(filePath);
|
|
429
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
430
|
+
const allowedExts = Array.isArray(rule.extension)
|
|
431
|
+
? rule.extension
|
|
432
|
+
: [rule.extension];
|
|
433
|
+
|
|
434
|
+
const validation = {
|
|
435
|
+
filePath,
|
|
436
|
+
ruleName,
|
|
437
|
+
valid: true,
|
|
438
|
+
checks: [],
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// Check extension
|
|
442
|
+
const extCheck = {
|
|
443
|
+
check: 'extension',
|
|
444
|
+
allowed: allowedExts,
|
|
445
|
+
actual: ext,
|
|
446
|
+
passed: allowedExts.includes(ext),
|
|
447
|
+
};
|
|
448
|
+
validation.checks.push(extCheck);
|
|
449
|
+
if (!extCheck.passed) validation.valid = false;
|
|
450
|
+
|
|
451
|
+
// Check size
|
|
452
|
+
const sizeCheck = {
|
|
453
|
+
check: 'size',
|
|
454
|
+
min: rule.minSize,
|
|
455
|
+
max: rule.maxSize,
|
|
456
|
+
actual: stats.size,
|
|
457
|
+
passed: stats.size >= rule.minSize && stats.size <= rule.maxSize,
|
|
458
|
+
};
|
|
459
|
+
validation.checks.push(sizeCheck);
|
|
460
|
+
if (!sizeCheck.passed) validation.valid = false;
|
|
461
|
+
|
|
462
|
+
return validation;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
return {
|
|
465
|
+
valid: false,
|
|
466
|
+
error: error.message,
|
|
467
|
+
filePath,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get filter statistics
|
|
474
|
+
*/
|
|
475
|
+
getStatistics() {
|
|
476
|
+
return {
|
|
477
|
+
...this.stats,
|
|
478
|
+
averageFilesPerFilter:
|
|
479
|
+
this.stats.filterApplications > 0
|
|
480
|
+
? Math.round(this.stats.filesFiltered / this.stats.filterApplications)
|
|
481
|
+
: 0,
|
|
482
|
+
matchRate:
|
|
483
|
+
this.stats.filesFiltered > 0
|
|
484
|
+
? (
|
|
485
|
+
(this.stats.filesMatched / this.stats.filesFiltered) *
|
|
486
|
+
100
|
|
487
|
+
).toFixed(2) + '%'
|
|
488
|
+
: 'N/A',
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Reset statistics
|
|
494
|
+
*/
|
|
495
|
+
resetStatistics() {
|
|
496
|
+
this.stats = {
|
|
497
|
+
filesFiltered: 0,
|
|
498
|
+
filesMatched: 0,
|
|
499
|
+
filesRejected: 0,
|
|
500
|
+
filterApplications: 0,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export default AdvancedFilterService;
|