diff --git a/extension.schema.json b/extension.schema.json index ed7c0c795..e57dc0f15 100644 --- a/extension.schema.json +++ b/extension.schema.json @@ -883,6 +883,20 @@ "description": "Sidebar extensions", "type": "object", "properties": { + "rules": { + "description": "Element rules", + "type": "object", + "properties": { + "enabled": { + "description": "Rule to evaluate the enabled state", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 0 + } + } + }, "toolbar": { "description": "Toolbar entries", "type": "array", diff --git a/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.spec.ts b/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.spec.ts index 39f033f8b..930ffbbcc 100644 --- a/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.spec.ts +++ b/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.spec.ts @@ -32,7 +32,7 @@ import { By } from '@angular/platform-browser'; import { AppExtensionService, NodePermissionService } from '@alfresco/aca-shared'; import { Actions } from '@ngrx/effects'; 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 { MatDialogModule } from '@angular/material/dialog'; @@ -70,6 +70,7 @@ describe('MetadataTabComponent', () => { return permissions.some((permission) => source.allowableOperations.includes(permission)); }); spyOn(contentMetadataService, 'getGroupedProperties').and.returnValue(of()); + extensions = TestBed.inject(AppExtensionService); }); afterEach(() => { @@ -79,7 +80,6 @@ describe('MetadataTabComponent', () => { describe('content-metadata configuration', () => { beforeEach(() => { appConfig = TestBed.inject(AppConfigService); - extensions = TestBed.inject(AppExtensionService); appConfig.config['content-metadata'] = { presets }; }); @@ -102,9 +102,12 @@ describe('MetadataTabComponent', () => { }); describe('readOnly', () => { + let extensionService: ExtensionService; + beforeEach(() => { fixture = TestBed.createComponent(MetadataTabComponent); component = fixture.componentInstance; + extensionService = TestBed.inject(ExtensionService); }); 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); }); + 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', () => { let editOfflineAction: EditOfflineAction; diff --git a/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.ts b/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.ts index 5c373d855..b7c0fc27e 100644 --- a/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.ts +++ b/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.ts @@ -40,6 +40,7 @@ import { CommonModule } from '@angular/common'; import { Actions, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ExtensionService } from '@alfresco/adf-extensions'; @Component({ standalone: true, @@ -81,15 +82,16 @@ export class MetadataTabComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); constructor( - private permission: NodePermissionService, - protected extensions: AppExtensionService, - private appConfig: AppConfigService, - private notificationService: NotificationService, - private contentMetadataService: ContentMetadataService, - private actions$: Actions, - private tagService: TagService, - private categoryService: CategoryService, - private store: Store + private readonly permission: NodePermissionService, + protected readonly extensions: AppExtensionService, + private readonly appConfig: AppConfigService, + private readonly notificationService: NotificationService, + private readonly contentMetadataService: ContentMetadataService, + private readonly actions$: Actions, + private readonly tagService: TagService, + private readonly categoryService: CategoryService, + private readonly store: Store, + private readonly extensionService: ExtensionService ) { if (this.extensions.contentMetadata) { this.appConfig.config['content-metadata'].presets = this.extensions.contentMetadata.presets; @@ -128,6 +130,12 @@ export class MetadataTabComponent implements OnInit { } 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); } }