Skip to content

Commit 94977a4

Browse files
committed
[us40] Disable updating the category when it was automatically assigned
1 parent 839b38b commit 94977a4

6 files changed

+99
-3
lines changed

src/app/file-list/file-list.component.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@
7979
<mat-icon>download</mat-icon>
8080
<span>Download</span>
8181
</a>
82-
<button (click)="setCategory(element)" class="set-category-file" mat-menu-item>
82+
<button (click)="setCategory(element)" [disabled]="hasMatchingRule(element)" class="set-category-file"
83+
mat-menu-item>
8384
<mat-icon>category</mat-icon>
8485
<span>Set category</span>
8586
</button>

src/app/file-list/file-list.component.spec.ts

+41
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {MatSortModule} from "@angular/material/sort";
3737
import {BreakpointObserver} from "@angular/cdk/layout";
3838
import {FilesCacheService} from "../files-cache/files-cache.service";
3939
import {mockFilesCacheService} from "../files-cache/files-cache.service.spec";
40+
import {RuleService} from "../rules/rule.service";
4041

4142
function mockRenderAndWaitForChanges() {
4243
let fixture = MockRender(FileListComponent, null, {reset: true});
@@ -65,6 +66,10 @@ describe('FileListComponent', () => {
6566
provide: FilesCacheService,
6667
useValue: mock<FilesCacheService>()
6768
})
69+
.provide({
70+
provide: RuleService,
71+
useValue: mock<RuleService>()
72+
})
6873
.replace(BrowserAnimationsModule, NoopAnimationsModule)
6974
);
7075

@@ -518,6 +523,27 @@ describe('FileListComponent', () => {
518523
let result = await page.getCategoriesInDialog();
519524
expect(result).toEqual(['cat1', 'cat1b'])
520525
}))
526+
527+
it('should prevent category assignment when the file categories were automatically assigned', fakeAsync(async () => {
528+
// Arrange
529+
let file = mockFileElement('name');
530+
mockFilesCacheService([file], true);
531+
532+
let ruleService = ngMocks.get(RuleService);
533+
let fileToMatchingRuleMap = new Map();
534+
fileToMatchingRuleMap.set(file.id, "existing rule");
535+
when(() => ruleService.getFileToMatchingRuleMap()).thenResolve(fileToMatchingRuleMap);
536+
537+
let fixture = mockRenderAndWaitForChanges();
538+
let page = new Page(fixture);
539+
540+
// Act
541+
Page.openItemMenu('name');
542+
let isMenuDisabled = await page.isMenuAssignCategoryDisabled();
543+
544+
// Assert
545+
expect(isMenuDisabled).toBeTruthy();
546+
}))
521547
})
522548

523549
describe('Filter by file name', () => {
@@ -859,6 +885,10 @@ class Page {
859885
await this.clickMenu('.set-category-file');
860886
}
861887

888+
isMenuAssignCategoryDisabled() {
889+
return this.isMenuDisabled('.set-category-file');
890+
}
891+
862892
async setCategoryInDialog(category: string) {
863893
let testElement = await this.typeCategoryInDialog(category);
864894
await testElement.sendKeys(TestKey.ENTER)
@@ -939,4 +969,15 @@ class Page {
939969
await matMenuHarness?.clickItem({selector: selector});
940970
}
941971

972+
private async isMenuDisabled(selector: string) {
973+
let matMenuHarnesses = await this.loader.getAllHarnesses(MatMenuHarness);
974+
// The menu should be the one opened
975+
let matMenuHarness = await findAsyncSequential(matMenuHarnesses, value => value.isOpen());
976+
if (!matMenuHarness) {
977+
throw new Error("No menu for selector: " + selector);
978+
}
979+
let menuItems = await matMenuHarness.getItems({selector: selector});
980+
return menuItems[0].isDisabled();
981+
}
982+
942983
}

src/app/file-list/file-list.component.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from "@angular/material/autocomplete";
2121
import {MatSort, MatSortable} from "@angular/material/sort";
2222
import {FilesCacheService} from "../files-cache/files-cache.service";
23+
import {RuleService} from "../rules/rule.service";
2324

2425
export interface FileOrFolderElement {
2526
id: string;
@@ -65,14 +66,15 @@ export class FileListComponent implements OnInit {
6566
isCategoryPanelExpanded = true;
6667
private categoryFilters = new Set<FolderElement>();
6768
private allFiles: FileOrFolderElement[] = [];
69+
private fileToMatchingRuleMap = new Map<string, string>();
6870

69-
constructor(private fileService: FileService, public dialog: MatDialog, private filesCacheService: FilesCacheService) {
71+
constructor(private fileService: FileService, public dialog: MatDialog, private filesCacheService: FilesCacheService, private ruleService: RuleService) {
7072
this.fileDataSource.filterPredicate = data => {
7173
return this.filterPredicate(data);
7274
}
7375
}
7476

75-
ngOnInit(): void {
77+
async ngOnInit() {
7678
this.baseFolderId = this.filesCacheService.getBaseFolder();
7779
this.allFiles = this.filesCacheService.getAll();
7880
this.populateFilesAndCategories();
@@ -83,6 +85,7 @@ export class FileListComponent implements OnInit {
8385
}
8486

8587
this.checkForEmptyCategoriesToRemove();
88+
this.fileToMatchingRuleMap = await this.ruleService.getFileToMatchingRuleMap();
8689
}
8790

8891
trashFile(element: FileElement) {
@@ -249,6 +252,11 @@ export class FileListComponent implements OnInit {
249252
return this.getCategories(fileEl).includes(category);
250253
})
251254
}
255+
256+
257+
hasMatchingRule(file: FileElement) {
258+
return this.fileToMatchingRuleMap.has(file.id);
259+
}
252260
}
253261

254262
@Component({

src/app/rules/rule.service.spec.ts

+29
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,35 @@ describe('RuleService', () => {
376376
// No failure in mock setup
377377
}));
378378
})
379+
380+
describe('getFileToMatchingRuleMap', () => {
381+
it('should return the mapping with one match', async () => {
382+
// Arrange
383+
const service = MockRender(RuleService).point.componentInstance;
384+
385+
let ruleRepository = ngMocks.findInstance(RuleRepository);
386+
when(() => ruleRepository.findAll())
387+
.thenResolve([{
388+
name: 'Matching rule',
389+
category: ['Test'],
390+
script: 'return true;',
391+
fileRuns: [{id: "id-file1", value: true}, {id: "id-file2", value: false}]
392+
}, {
393+
name: 'False rule',
394+
category: ['False'],
395+
script: 'return false;',
396+
fileRuns: [{id: "id-file1", value: false}, {id: "id-file2", value: false}]
397+
}]);
398+
399+
// Act
400+
let fileToMatchingRuleMap = await service.getFileToMatchingRuleMap();
401+
402+
// Assert
403+
expect(fileToMatchingRuleMap)
404+
.toEqual(new Map([['id-file1', 'Matching rule']]));
405+
})
406+
407+
});
379408
});
380409

381410
let ruleServiceMock: RuleService;

src/app/rules/rule.service.ts

+16
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ export class RuleService {
8686
return this.ruleRepository.update(rule);
8787
}
8888

89+
async getFileToMatchingRuleMap(): Promise<Map<string, string>> {
90+
let result = new Map<string, string>();
91+
let rules = await this.ruleRepository.findAll();
92+
// Search for rules which have fileRuns evaluated to true
93+
for (let rule of rules) {
94+
if (rule.fileRuns) {
95+
for (const fileRun of rule.fileRuns) {
96+
if (fileRun.value) {
97+
result.set(fileRun.id, rule.name);
98+
}
99+
}
100+
}
101+
}
102+
return result;
103+
}
104+
89105
/**
90106
* Run the given rules on the given files and set the associated category for each file that got a matching rule
91107
*/

src/app/user-root/user-root.component.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class UserRootComponent {
1616
databaseBackupAndRestoreService.restore()
1717
.subscribe(() => {
1818
ruleService.runAll().subscribe();
19+
// TODO: refresh after if there was any update to one of the file categories
1920
});
2021
}
2122
}

0 commit comments

Comments
 (0)