[MNT-24923] legal hold hold capabilities are not verified properly (#4616)

* [MNT-24923] Added possibility to configure readonly for metadata sidebar by json config

* [MNT-24923] Unit tests

* [MNT-24923] Fixed sonar issues
This commit is contained in:
AleksanderSklorz
2025-06-12 14:30:11 +02:00
committed by GitHub
parent 690acfb64b
commit 3a51c19bc6
3 changed files with 201 additions and 12 deletions

View File

@@ -883,6 +883,20 @@
"description": "Sidebar extensions", "description": "Sidebar extensions",
"type": "object", "type": "object",
"properties": { "properties": {
"rules": {
"description": "Element rules",
"type": "object",
"properties": {
"enabled": {
"description": "Rule to evaluate the enabled state",
"type": "array",
"items": {
"type": "string"
},
"minItems": 0
}
}
},
"toolbar": { "toolbar": {
"description": "Toolbar entries", "description": "Toolbar entries",
"type": "array", "type": "array",

View File

@@ -32,7 +32,7 @@ import { By } from '@angular/platform-browser';
import { AppExtensionService, NodePermissionService } from '@alfresco/aca-shared'; import { AppExtensionService, NodePermissionService } from '@alfresco/aca-shared';
import { Actions } from '@ngrx/effects'; import { Actions } from '@ngrx/effects';
import { of, Subject } from 'rxjs'; import { of, Subject } from 'rxjs';
import { ContentActionType } from '@alfresco/adf-extensions'; import { ContentActionType, ExtensionService } from '@alfresco/adf-extensions';
import { CategoryService, ContentMetadataComponent, ContentMetadataService, TagService } from '@alfresco/adf-content-services'; import { CategoryService, ContentMetadataComponent, ContentMetadataService, TagService } from '@alfresco/adf-content-services';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
@@ -70,6 +70,7 @@ describe('MetadataTabComponent', () => {
return permissions.some((permission) => source.allowableOperations.includes(permission)); return permissions.some((permission) => source.allowableOperations.includes(permission));
}); });
spyOn(contentMetadataService, 'getGroupedProperties').and.returnValue(of()); spyOn(contentMetadataService, 'getGroupedProperties').and.returnValue(of());
extensions = TestBed.inject(AppExtensionService);
}); });
afterEach(() => { afterEach(() => {
@@ -79,7 +80,6 @@ describe('MetadataTabComponent', () => {
describe('content-metadata configuration', () => { describe('content-metadata configuration', () => {
beforeEach(() => { beforeEach(() => {
appConfig = TestBed.inject(AppConfigService); appConfig = TestBed.inject(AppConfigService);
extensions = TestBed.inject(AppExtensionService);
appConfig.config['content-metadata'] = { presets }; appConfig.config['content-metadata'] = { presets };
}); });
@@ -102,9 +102,12 @@ describe('MetadataTabComponent', () => {
}); });
describe('readOnly', () => { describe('readOnly', () => {
let extensionService: ExtensionService;
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(MetadataTabComponent); fixture = TestBed.createComponent(MetadataTabComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
extensionService = TestBed.inject(ExtensionService);
}); });
it('should return false if node is not locked and has update permission', async () => { it('should return false if node is not locked and has update permission', async () => {
@@ -146,6 +149,170 @@ describe('MetadataTabComponent', () => {
expect(component.readOnly).toBe(true); expect(component.readOnly).toBe(true);
}); });
it('should set readOnly to false if node is defined, is not locked and enabled rule for sidebar returns true, has update permission for node', () => {
component.node = {
id: 'some id',
isLocked: false,
allowableOperations: ['update']
} as Node;
const rule = 'someRule';
spyOn(extensionService, 'getFeature').and.returnValue({
rules: {
enabled: [rule]
}
});
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeFalse();
expect(extensionService.getFeature).toHaveBeenCalledWith('sidebar');
expect(extensionService.evaluateRule).toHaveBeenCalledWith(rule, extensions);
});
it('should set readOnly to false if node is defined, is not locked and there is nothing for sidebar in configuration, has update permission for node', () => {
component.node = {
id: 'some id',
isLocked: false,
allowableOperations: ['update']
} as Node;
spyOn(extensionService, 'getFeature').and.returnValue(undefined);
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeFalse();
expect(extensionService.getFeature).toHaveBeenCalledWith('sidebar');
expect(extensionService.evaluateRule).not.toHaveBeenCalled();
});
it('should set readOnly to true if node is undefined and enabled rule for sidebar returns true, has update permission for node', () => {
component.node = undefined;
spyOn(extensionService, 'getFeature').and.returnValue({
rules: {
enabled: ['someRule']
}
});
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeTrue();
expect(extensionService.getFeature).not.toHaveBeenCalled();
expect(extensionService.evaluateRule).not.toHaveBeenCalled();
});
it('should set readOnly to true if node is undefined and there is nothing for sidebar in configuration, has update permission for node', () => {
component.node = undefined;
spyOn(extensionService, 'getFeature').and.returnValue(undefined);
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeTrue();
expect(extensionService.getFeature).not.toHaveBeenCalled();
expect(extensionService.evaluateRule).not.toHaveBeenCalled();
});
it('should set readOnly to true if node is defined, is locked and enabled rule for sidebar returns true, has update permission for node', () => {
component.node = {
id: 'some id',
isLocked: true,
allowableOperations: ['update']
} as Node;
spyOn(extensionService, 'getFeature').and.returnValue({
rules: {
enabled: ['someRule']
}
});
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeTrue();
expect(extensionService.getFeature).not.toHaveBeenCalled();
expect(extensionService.evaluateRule).not.toHaveBeenCalled();
});
it('should set readOnly to true if node is defined, is locked and there is nothing for sidebar in configuration, has update permission for node', () => {
component.node = {
id: 'some id',
isLocked: true,
allowableOperations: ['update']
} as Node;
spyOn(extensionService, 'getFeature').and.returnValue(undefined);
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeTrue();
expect(extensionService.getFeature).not.toHaveBeenCalled();
expect(extensionService.evaluateRule).not.toHaveBeenCalled();
});
it('should set readOnly to true if node is defined, is not locked and enabled rule for sidebar returns true, has not update permission for node', () => {
component.node = {
id: 'some id',
isLocked: false,
allowableOperations: []
} as Node;
const rule = 'someRule';
spyOn(extensionService, 'getFeature').and.returnValue({
rules: {
enabled: [rule]
}
});
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeTrue();
expect(extensionService.getFeature).toHaveBeenCalledWith('sidebar');
expect(extensionService.evaluateRule).toHaveBeenCalledWith(rule, extensions);
});
it('should set readOnly to true if node is defined, is not locked and there is nothing for sidebar in configuration, has not update permission for node', () => {
component.node = {
id: 'some id',
isLocked: false,
allowableOperations: ['']
} as Node;
spyOn(extensionService, 'getFeature').and.returnValue(undefined);
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeTrue();
expect(extensionService.getFeature).toHaveBeenCalledWith('sidebar');
expect(extensionService.evaluateRule).not.toHaveBeenCalled();
});
it('should set readOnly to true if node is defined, is locked and enabled rule for sidebar returns true, has not update permission for node', () => {
component.node = {
id: 'some id',
isLocked: true,
allowableOperations: []
} as Node;
spyOn(extensionService, 'getFeature').and.returnValue({
rules: {
enabled: ['someRule']
}
});
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeTrue();
expect(extensionService.getFeature).not.toHaveBeenCalled();
expect(extensionService.evaluateRule).not.toHaveBeenCalled();
});
it('should set readOnly to true if node is defined, is locked and there is nothing for sidebar in configuration, has not update permission for node', () => {
component.node = {
id: 'some id',
isLocked: true,
allowableOperations: []
} as Node;
spyOn(extensionService, 'getFeature').and.returnValue(undefined);
spyOn(extensionService, 'evaluateRule').and.returnValue(true);
component.ngOnInit();
expect(component.readOnly).toBeTrue();
expect(extensionService.getFeature).not.toHaveBeenCalled();
expect(extensionService.evaluateRule).not.toHaveBeenCalled();
});
describe('set by triggering EditOfflineAction', () => { describe('set by triggering EditOfflineAction', () => {
let editOfflineAction: EditOfflineAction; let editOfflineAction: EditOfflineAction;

View File

@@ -40,6 +40,7 @@ import { CommonModule } from '@angular/common';
import { Actions, ofType } from '@ngrx/effects'; import { Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ExtensionService } from '@alfresco/adf-extensions';
@Component({ @Component({
standalone: true, standalone: true,
@@ -81,15 +82,16 @@ export class MetadataTabComponent implements OnInit {
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
constructor( constructor(
private permission: NodePermissionService, private readonly permission: NodePermissionService,
protected extensions: AppExtensionService, protected readonly extensions: AppExtensionService,
private appConfig: AppConfigService, private readonly appConfig: AppConfigService,
private notificationService: NotificationService, private readonly notificationService: NotificationService,
private contentMetadataService: ContentMetadataService, private readonly contentMetadataService: ContentMetadataService,
private actions$: Actions, private readonly actions$: Actions,
private tagService: TagService, private readonly tagService: TagService,
private categoryService: CategoryService, private readonly categoryService: CategoryService,
private store: Store<AppStore> private readonly store: Store<AppStore>,
private readonly extensionService: ExtensionService
) { ) {
if (this.extensions.contentMetadata) { if (this.extensions.contentMetadata) {
this.appConfig.config['content-metadata'].presets = this.extensions.contentMetadata.presets; this.appConfig.config['content-metadata'].presets = this.extensions.contentMetadata.presets;
@@ -128,6 +130,12 @@ export class MetadataTabComponent implements OnInit {
} }
private checkIfNodeIsUpdatable(node: Node) { private checkIfNodeIsUpdatable(node: Node) {
this.readOnly = !(node && !isLocked({ entry: node }) ? this.permission.check(node, ['update']) : false); this.readOnly = !(node &&
!isLocked({ entry: node }) &&
(this.extensionService.getFeature('sidebar')?.['rules']?.enabled ?? []).every((rule: string) =>
this.extensionService.evaluateRule(rule, this.extensions)
)
? this.permission.check(node, ['update'])
: false);
} }
} }