From 496853671edbd01e4b181cb70f10526911f9544a Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 26 Apr 2023 12:35:50 +0200 Subject: [PATCH 01/45] [CST-9636] Added access control components in community, item and collection edit page --- .../collection-access-control.component.html | 3 ++ .../collection-access-control.component.scss | 0 ...ollection-access-control.component.spec.ts | 25 +++++++++++++ .../collection-access-control.component.ts | 37 +++++++++++++++++++ .../edit-collection-page.module.ts | 3 +- .../edit-collection-page.routing.module.ts | 6 +++ .../community-access-control.component.html | 1 + .../community-access-control.component.scss | 0 ...community-access-control.component.spec.ts | 25 +++++++++++++ .../community-access-control.component.ts | 15 ++++++++ .../edit-community-page.module.ts | 4 +- .../edit-community-page.routing.module.ts | 6 +++ .../edit-item-page/edit-item-page.module.ts | 4 +- .../edit-item-page.routing.module.ts | 6 +++ .../item-access-control.component.html | 1 + .../item-access-control.component.scss | 0 .../item-access-control.component.spec.ts | 25 +++++++++++++ .../item-access-control.component.ts | 15 ++++++++ src/assets/i18n/en.json5 | 12 ++++++ 19 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html create mode 100644 src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.scss create mode 100644 src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.spec.ts create mode 100644 src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts create mode 100644 src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html create mode 100644 src/app/community-page/edit-community-page/community-access-control/community-access-control.component.scss create mode 100644 src/app/community-page/edit-community-page/community-access-control/community-access-control.component.spec.ts create mode 100644 src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control.component.scss create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control.component.spec.ts create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html new file mode 100644 index 0000000000..0719a18d94 --- /dev/null +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html @@ -0,0 +1,3 @@ +
+ Access control page!!! +
diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.scss b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.spec.ts new file mode 100644 index 0000000000..5ae7480d66 --- /dev/null +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CollectionAccessControlComponent } from './collection-access-control.component'; + +describe('CollectionAccessControlComponent', () => { + let component: CollectionAccessControlComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CollectionAccessControlComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionAccessControlComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts new file mode 100644 index 0000000000..8675e3a83f --- /dev/null +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { ActivatedRoute } from '@angular/router'; +import { first, map } from 'rxjs/operators'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; + +@Component({ + selector: 'ds-collection-access-control', + templateUrl: './collection-access-control.component.html', + styleUrls: ['./collection-access-control.component.scss'] +}) +export class CollectionAccessControlComponent implements OnInit { + + /** + * The initial DSO object + */ + public dsoRD$: Observable>; + + /** + * Initialize instance variables + * + * @param {ActivatedRoute} route + */ + constructor( + private route: ActivatedRoute + ) { + } + + /** + * Initialize the component, setting up the collection + */ + ngOnInit(): void { + this.dsoRD$ = this.route.parent.parent.data.pipe(first(), map((data) => data.dso)); + } + +} diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts index 18f7feb699..3817122368 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts @@ -13,6 +13,7 @@ import { CollectionSourceControlsComponent } from './collection-source/collectio import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module'; import { FormModule } from '../../shared/form/form.module'; import { ComcolModule } from '../../shared/comcol/comcol.module'; +import { CollectionAccessControlComponent } from './collection-access-control/collection-access-control.component'; /** * Module that contains all components related to the Edit Collection page administrator functionality @@ -33,7 +34,7 @@ import { ComcolModule } from '../../shared/comcol/comcol.module'; CollectionRolesComponent, CollectionCurateComponent, CollectionSourceComponent, - + CollectionAccessControlComponent, CollectionSourceControlsComponent, CollectionAuthorizationsComponent ] diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page.routing.module.ts b/src/app/collection-page/edit-collection-page/edit-collection-page.routing.module.ts index 92fc6efeff..c4481985c0 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page.routing.module.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page.routing.module.ts @@ -13,6 +13,7 @@ import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/cr import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver'; import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component'; import { CollectionAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard'; +import { CollectionAccessControlComponent } from './collection-access-control/collection-access-control.component'; /** * Routing module that handles the routing for the Edit Collection page administrator functionality @@ -58,6 +59,11 @@ import { CollectionAdministratorGuard } from '../../core/data/feature-authorizat component: CollectionCurateComponent, data: { title: 'collection.edit.tabs.curate.title', showBreadcrumbs: true } }, + { + path: 'access-control', + component: CollectionAccessControlComponent, + data: { title: 'collection.edit.tabs.access-control.title', showBreadcrumbs: true } + }, /* { path: 'authorizations', component: CollectionAuthorizationsComponent, diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html new file mode 100644 index 0000000000..cec7f44321 --- /dev/null +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html @@ -0,0 +1 @@ +

community-access-control works!

diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.scss b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.spec.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.spec.ts new file mode 100644 index 0000000000..7ec727bbb5 --- /dev/null +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommunityAccessControlComponent } from './community-access-control.component'; + +describe('CommunityAccessControlComponent', () => { + let component: CommunityAccessControlComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CommunityAccessControlComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityAccessControlComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts new file mode 100644 index 0000000000..543d63564e --- /dev/null +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ds-community-access-control', + templateUrl: './community-access-control.component.html', + styleUrls: ['./community-access-control.component.scss'] +}) +export class CommunityAccessControlComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/community-page/edit-community-page/edit-community-page.module.ts b/src/app/community-page/edit-community-page/edit-community-page.module.ts index 0479ea6bc6..a9f020a9e6 100644 --- a/src/app/community-page/edit-community-page/edit-community-page.module.ts +++ b/src/app/community-page/edit-community-page/edit-community-page.module.ts @@ -10,6 +10,7 @@ import { CommunityAuthorizationsComponent } from './community-authorizations/com import { CommunityFormModule } from '../community-form/community-form.module'; import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../shared/comcol/comcol.module'; +import { CommunityAccessControlComponent } from './community-access-control/community-access-control.component'; /** * Module that contains all components related to the Edit Community page administrator functionality @@ -28,7 +29,8 @@ import { ComcolModule } from '../../shared/comcol/comcol.module'; CommunityCurateComponent, CommunityMetadataComponent, CommunityRolesComponent, - CommunityAuthorizationsComponent + CommunityAuthorizationsComponent, + CommunityAccessControlComponent ] }) export class EditCommunityPageModule { diff --git a/src/app/community-page/edit-community-page/edit-community-page.routing.module.ts b/src/app/community-page/edit-community-page/edit-community-page.routing.module.ts index faebb1ef2e..994c6b5e96 100644 --- a/src/app/community-page/edit-community-page/edit-community-page.routing.module.ts +++ b/src/app/community-page/edit-community-page/edit-community-page.routing.module.ts @@ -11,6 +11,7 @@ import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/cr import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver'; import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component'; import { CommunityAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/community-administrator.guard'; +import { CommunityAccessControlComponent } from './community-access-control/community-access-control.component'; /** * Routing module that handles the routing for the Edit Community page administrator functionality @@ -51,6 +52,11 @@ import { CommunityAdministratorGuard } from '../../core/data/feature-authorizati component: CommunityCurateComponent, data: { title: 'community.edit.tabs.curate.title', showBreadcrumbs: true } }, + { + path: 'access-control', + component: CommunityAccessControlComponent, + data: { title: 'collection.edit.tabs.access-control.title', showBreadcrumbs: true } + }, /*{ path: 'authorizations', component: CommunityAuthorizationsComponent, diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 24d27b3340..d922cee0a5 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -38,6 +38,7 @@ import { IdentifierDataService } from '../../core/data/identifier-data.service'; import { IdentifierDataComponent } from '../../shared/object-list/identifier-data/identifier-data.component'; import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component'; import { DsoSharedModule } from '../../dso-shared/dso-shared.module'; +import { ItemAccessControlComponent } from './item-access-control/item-access-control.component'; /** @@ -81,7 +82,8 @@ import { DsoSharedModule } from '../../dso-shared/dso-shared.module'; VirtualMetadataComponent, ItemAuthorizationsComponent, IdentifierDataComponent, - ItemRegisterDoiComponent + ItemRegisterDoiComponent, + ItemAccessControlComponent ], providers: [ BundleDataService, diff --git a/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts index 88172e2620..ce1d258b6b 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts @@ -41,6 +41,7 @@ import { ItemPageVersionHistoryGuard } from './item-page-version-history.guard'; import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.guard'; import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component'; import { ItemPageRegisterDoiGuard } from './item-page-register-doi.guard'; +import { ItemAccessControlComponent } from './item-access-control/item-access-control.component'; /** * Routing module that handles the routing for the Edit Item page administrator functionality @@ -106,6 +107,11 @@ import { ItemPageRegisterDoiGuard } from './item-page-register-doi.guard'; data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true }, canActivate: [ItemPageVersionHistoryGuard] }, + { + path: 'access-control', + component: ItemAccessControlComponent, + data: { title: 'item.edit.tabs.access-control.title', showBreadcrumbs: true } + }, { path: 'mapper', component: ItemCollectionMapperComponent, diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html new file mode 100644 index 0000000000..416b785722 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html @@ -0,0 +1 @@ +

item-access-control works!

diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.scss b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.spec.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.spec.ts new file mode 100644 index 0000000000..297ffa3822 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ItemAccessControlComponent } from './item-access-control.component'; + +describe('ItemAccessControlComponent', () => { + let component: ItemAccessControlComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ItemAccessControlComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemAccessControlComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts new file mode 100644 index 0000000000..5b42e5bf4e --- /dev/null +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ds-item-access-control', + templateUrl: './item-access-control.component.html', + styleUrls: ['./item-access-control.component.scss'] +}) +export class ItemAccessControlComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 66824e56b3..2c7ae5459f 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -965,6 +965,10 @@ "collection.edit.return": "Back", + "collection.edit.tabs.access-control.head": "Access Control", + + "collection.edit.tabs.access-control.title": "Collection Edit - Access Control", + "collection.edit.tabs.curate.head": "Curate", @@ -1201,6 +1205,10 @@ "community.edit.tabs.curate.title": "Community Edit - Curate", + "community.edit.tabs.access-control.head": "Access Control", + + "community.edit.tabs.access-control.title": "Community Edit - Access Control", + "community.edit.tabs.metadata.head": "Edit Metadata", "community.edit.tabs.metadata.title": "Community Edit - Metadata", @@ -2292,6 +2300,10 @@ "item.edit.tabs.curate.title": "Item Edit - Curate", + "item.edit.tabs.access-control.head": "Access Control", + + "item.edit.tabs.access-control.title": "Item Edit - Access Control", + "item.edit.tabs.metadata.head": "Metadata", "item.edit.tabs.metadata.title": "Item Edit - Metadata", From 3647292b8fa056503a31ea72e8b65b6fac5f9f6e Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Fri, 28 Apr 2023 16:47:07 +0200 Subject: [PATCH 02/45] [CST-9636] Added unit tests and the logic to create file object from payload --- .../collection-access-control.component.html | 91 +++++++++++++- .../collection-access-control.component.ts | 69 +++++++---- .../collection-access-control.service.ts | 54 +++++++++ .../edit-collection-page.module.ts | 6 + .../community-access-control.component.html | 93 ++++++++++++++- .../community-access-control.component.ts | 47 +++++++- .../community-access-control.service.ts | 54 +++++++++ .../edit-community-page.module.ts | 6 + .../edit-item-page/edit-item-page.module.ts | 6 + .../item-access-control.component.html | 111 +++++++++++++++++- .../item-access-control.component.ts | 55 ++++++++- .../item-access-control.service.ts | 71 +++++++++++ .../access-control-array-form.component.html | 68 +++++++++++ .../access-control-array-form.component.scss | 0 ...ccess-control-array-form.component.spec.ts | 101 ++++++++++++++++ .../access-control-array-form.component.ts | 88 ++++++++++++++ .../control-max-end-date.pipe.ts | 26 ++++ .../control-max-start-date.pipe.ts | 26 ++++ 18 files changed, 938 insertions(+), 34 deletions(-) create mode 100644 src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts create mode 100644 src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts create mode 100644 src/app/shared/access-control-array-form/access-control-array-form.component.html create mode 100644 src/app/shared/access-control-array-form/access-control-array-form.component.scss create mode 100644 src/app/shared/access-control-array-form/access-control-array-form.component.spec.ts create mode 100644 src/app/shared/access-control-array-form/access-control-array-form.component.ts create mode 100644 src/app/shared/access-control-array-form/control-max-end-date.pipe.ts create mode 100644 src/app/shared/access-control-array-form/control-max-start-date.pipe.ts diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html index 0719a18d94..52eda57f2c 100644 --- a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html @@ -1,3 +1,92 @@
- Access control page!!! +
+
+

+ This form allows you to perform changes to the access condition of all the items owned by collection under this community. + Changes can be performed on the access condition for the metadata (item) or for the content (bitstream). +

+ +
+
+
+

Item's Metadata

+ +
+ +
+
Mode
+
+
+ + +
+
+ + +
+
+
+ +
+
Access conditions
+ +
+ You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. +
+
+ + + + +
+
+
+

Bitstreams

+ +
+ +
+
Mode
+
+
+ + +
+
+ + +
+
+
+ +
+
Access conditions
+ +
+ You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. +
+
+ + + + +
+
+ +
+ +
+ + +
+
+
diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts index 8675e3a83f..0791e29233 100644 --- a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts @@ -1,37 +1,56 @@ -import { Component, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; -import { RemoteData } from '../../../core/data/remote-data'; -import { ActivatedRoute } from '@angular/router'; -import { first, map } from 'rxjs/operators'; -import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { shareReplay } from 'rxjs'; +import { + AccessControlArrayFormComponent +} from '../../../shared/access-control-array-form/access-control-array-form.component'; +import { CollectionAccessControlService } from './collection-access-control.service'; @Component({ selector: 'ds-collection-access-control', templateUrl: './collection-access-control.component.html', - styleUrls: ['./collection-access-control.component.scss'] + styleUrls: ['./collection-access-control.component.scss'], + providers: [CollectionAccessControlService] }) -export class CollectionAccessControlComponent implements OnInit { +export class CollectionAccessControlComponent implements OnInit { - /** - * The initial DSO object - */ - public dsoRD$: Observable>; + @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; + @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; + + constructor(private collectionAccessControlService: CollectionAccessControlService) {} + + state = initialState; + + dropdownData$ = this.collectionAccessControlService.dropdownData$.pipe( + shareReplay(1) + ); + + ngOnInit(): void { - /** - * Initialize instance variables - * - * @param {ActivatedRoute} route - */ - constructor( - private route: ActivatedRoute - ) { } - /** - * Initialize the component, setting up the collection - */ - ngOnInit(): void { - this.dsoRD$ = this.route.parent.parent.data.pipe(first(), map((data) => data.dso)); + reset() { + this.bitstreamAccessCmp.reset(); + this.itemAccessCmp.reset(); + this.state = initialState; + } + + submit() { + const bitstreamAccess = this.bitstreamAccessCmp.getValue(); + const itemAccess = this.itemAccessCmp.getValue(); + + console.log('bitstreamAccess', bitstreamAccess); + console.log('itemAccess', itemAccess); } } + +const initialState = { + item: { + toggleStatus: false, + accessMode: '', + }, + bitstream: { + toggleStatus: false, + accessMode: '', + }, +}; diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts new file mode 100644 index 0000000000..9cc7b8d85a --- /dev/null +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { AccessControlItem } from '../../../shared/access-control-array-form/access-control-array-form.component'; +import { Observable, of } from 'rxjs'; + +export interface AccessControlDropdownDataResponse { + id: string; + itemAccessConditionOptions: AccessControlItem[]; + bitstreamAccessConditionOptions: AccessControlItem[]; +} + +@Injectable() +export class CollectionAccessControlService { + dropdownData$: Observable = of(accessControlDropdownData); +} + +const accessControlDropdownData: AccessControlDropdownDataResponse = { + 'id': 'default', + 'itemAccessConditionOptions': [ + { + 'name': 'openaccess' + }, + { + 'name': 'administrator' + }, + { + 'name': 'embargo', + 'hasStartDate': true, + 'maxStartDate': '2018-06-24T00:40:54.970+0000' + }, + { + 'name': 'lease', + 'hasEndDate': true, + 'maxEndDate': '2017-12-24T00:40:54.970+0000' + } + ], + 'bitstreamAccessConditionOptions': [ + { + 'name': 'openaccess' + }, + { + 'name': 'administrator' + }, + { + 'name': 'embargo', + 'hasStartDate': true, + 'maxStartDate': '2018-06-24T00:40:54.970+0000' + }, + { + 'name': 'lease', + 'hasEndDate': true, + 'maxEndDate': '2017-12-24T00:40:54.970+0000' + } + ] +}; diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts index 3817122368..5af7d3189a 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts @@ -14,6 +14,10 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource- import { FormModule } from '../../shared/form/form.module'; import { ComcolModule } from '../../shared/comcol/comcol.module'; import { CollectionAccessControlComponent } from './collection-access-control/collection-access-control.component'; +import { + AccessControlArrayFormModule +} from '../../shared/access-control-array-form/access-control-array-form.component'; +import { UiSwitchModule } from 'ngx-ui-switch'; /** * Module that contains all components related to the Edit Collection page administrator functionality @@ -27,6 +31,8 @@ import { CollectionAccessControlComponent } from './collection-access-control/co ResourcePoliciesModule, FormModule, ComcolModule, + AccessControlArrayFormModule, + UiSwitchModule, ], declarations: [ EditCollectionPageComponent, diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html index cec7f44321..52eda57f2c 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html @@ -1 +1,92 @@ -

community-access-control works!

+
+
+
+

+ This form allows you to perform changes to the access condition of all the items owned by collection under this community. + Changes can be performed on the access condition for the metadata (item) or for the content (bitstream). +

+ +
+
+
+

Item's Metadata

+ +
+ +
+
Mode
+
+
+ + +
+
+ + +
+
+
+ +
+
Access conditions
+ +
+ You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. +
+
+ + + + +
+
+
+

Bitstreams

+ +
+ +
+
Mode
+
+
+ + +
+
+ + +
+
+
+ +
+
Access conditions
+ +
+ You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. +
+
+ + + + +
+
+ +
+ +
+ + +
+
+
+
diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts index 543d63564e..202c04b7b5 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts @@ -1,15 +1,56 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { CommunityAccessControlService } from './community-access-control.service'; +import { shareReplay } from 'rxjs'; +import { + AccessControlArrayFormComponent +} from '../../../shared/access-control-array-form/access-control-array-form.component'; @Component({ selector: 'ds-community-access-control', templateUrl: './community-access-control.component.html', - styleUrls: ['./community-access-control.component.scss'] + styleUrls: ['./community-access-control.component.scss'], + providers: [CommunityAccessControlService] }) export class CommunityAccessControlComponent implements OnInit { - constructor() { } + @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; + @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; + + constructor(private communityAccessControlService: CommunityAccessControlService) {} + + state = initialState; + + dropdownData$ = this.communityAccessControlService.dropdownData$.pipe( + shareReplay(1) + ); ngOnInit(): void { + + } + + reset() { + this.bitstreamAccessCmp.reset(); + this.itemAccessCmp.reset(); + this.state = initialState; + } + + submit() { + const bitstreamAccess = this.bitstreamAccessCmp.getValue(); + const itemAccess = this.itemAccessCmp.getValue(); + + console.log('bitstreamAccess', bitstreamAccess); + console.log('itemAccess', itemAccess); } } + +const initialState = { + item: { + toggleStatus: false, + accessMode: '', + }, + bitstream: { + toggleStatus: false, + accessMode: '', + }, +}; diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts new file mode 100644 index 0000000000..d78d42ba90 --- /dev/null +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { AccessControlItem } from '../../../shared/access-control-array-form/access-control-array-form.component'; +import { Observable, of } from 'rxjs'; + +export interface AccessControlDropdownDataResponse { + id: string; + itemAccessConditionOptions: AccessControlItem[]; + bitstreamAccessConditionOptions: AccessControlItem[]; +} + +@Injectable() +export class CommunityAccessControlService { + dropdownData$: Observable = of(accessControlDropdownData); +} + +const accessControlDropdownData: AccessControlDropdownDataResponse = { + 'id': 'default', + 'itemAccessConditionOptions': [ + { + 'name': 'openaccess' + }, + { + 'name': 'administrator' + }, + { + 'name': 'embargo', + 'hasStartDate': true, + 'maxStartDate': '2018-06-24T00:40:54.970+0000' + }, + { + 'name': 'lease', + 'hasEndDate': true, + 'maxEndDate': '2017-12-24T00:40:54.970+0000' + } + ], + 'bitstreamAccessConditionOptions': [ + { + 'name': 'openaccess' + }, + { + 'name': 'administrator' + }, + { + 'name': 'embargo', + 'hasStartDate': true, + 'maxStartDate': '2018-06-24T00:40:54.970+0000' + }, + { + 'name': 'lease', + 'hasEndDate': true, + 'maxEndDate': '2017-12-24T00:40:54.970+0000' + } + ] +}; diff --git a/src/app/community-page/edit-community-page/edit-community-page.module.ts b/src/app/community-page/edit-community-page/edit-community-page.module.ts index a9f020a9e6..8aa52086ee 100644 --- a/src/app/community-page/edit-community-page/edit-community-page.module.ts +++ b/src/app/community-page/edit-community-page/edit-community-page.module.ts @@ -11,6 +11,10 @@ import { CommunityFormModule } from '../community-form/community-form.module'; import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../shared/comcol/comcol.module'; import { CommunityAccessControlComponent } from './community-access-control/community-access-control.component'; +import { UiSwitchModule } from 'ngx-ui-switch'; +import { + AccessControlArrayFormModule +} from '../../shared/access-control-array-form/access-control-array-form.component'; /** * Module that contains all components related to the Edit Community page administrator functionality @@ -23,6 +27,8 @@ import { CommunityAccessControlComponent } from './community-access-control/comm CommunityFormModule, ComcolModule, ResourcePoliciesModule, + UiSwitchModule, + AccessControlArrayFormModule, ], declarations: [ EditCommunityPageComponent, diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index d922cee0a5..65862b3de8 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -39,6 +39,10 @@ import { IdentifierDataComponent } from '../../shared/object-list/identifier-dat import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component'; import { DsoSharedModule } from '../../dso-shared/dso-shared.module'; import { ItemAccessControlComponent } from './item-access-control/item-access-control.component'; +import { + AccessControlArrayFormModule +} from '../../shared/access-control-array-form/access-control-array-form.component'; +import { UiSwitchModule } from 'ngx-ui-switch'; /** @@ -56,6 +60,8 @@ import { ItemAccessControlComponent } from './item-access-control/item-access-co NgbModule, ItemVersionsModule, DsoSharedModule, + AccessControlArrayFormModule, + UiSwitchModule, ], declarations: [ EditItemPageComponent, diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html index 416b785722..f5a02b0452 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html @@ -1 +1,110 @@ -

item-access-control works!

+
+
+
+

+ This form allows you to perform changes to the access condition of all the items owned by collection under this community. + Changes can be performed on the access condition for the metadata (item) or for the content (bitstream). +

+ +
+
+
+

Item's Metadata

+ +
+ +
+
Mode
+
+
+ + +
+
+ + +
+
+
+ +
+
Access conditions
+ +
+ You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. +
+
+ + + + +
+
+
+

Bitstreams

+ +
+ +
+
Limit the changes to specific bitstreams
+
+
+ + +
+
+ + +
+
+
+ +
+
Mode
+
+
+ + +
+
+ + +
+
+
+ +
+
Access conditions
+ +
+ You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. +
+
+ + + + +
+
+ +
+ +
+ + +
+
+
+
diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts index 5b42e5bf4e..6882dfb2a1 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts @@ -1,15 +1,64 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { + AccessControlArrayFormComponent +} from '../../../shared/access-control-array-form/access-control-array-form.component'; +import { + CollectionAccessControlService +} from '../../../collection-page/edit-collection-page/collection-access-control/collection-access-control.service'; +import { shareReplay } from 'rxjs'; +import { ItemAccessControlService } from './item-access-control.service'; @Component({ selector: 'ds-item-access-control', templateUrl: './item-access-control.component.html', - styleUrls: ['./item-access-control.component.scss'] + styleUrls: ['./item-access-control.component.scss'], + providers: [ItemAccessControlService] }) export class ItemAccessControlComponent implements OnInit { - constructor() { } + @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; + @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; + + constructor(private itemAccessControlService: ItemAccessControlService) {} + + state = initialState; + + dropdownData$ = this.itemAccessControlService.dropdownData$.pipe( + shareReplay(1) + ); ngOnInit(): void { + + } + + reset() { + this.bitstreamAccessCmp.reset(); + this.itemAccessCmp.reset(); + this.state = initialState; + } + + submit() { + const bitstreamAccess = this.bitstreamAccessCmp.getValue(); + const itemAccess = this.itemAccessCmp.getValue(); + + this.itemAccessControlService.execute({ + bitstreamAccess, + itemAccess, + state: this.state + }); } } + +const initialState = { + item: { + toggleStatus: false, + accessMode: '', + }, + bitstream: { + toggleStatus: false, + accessMode: '', + changesLimit: '', // 'all' | 'selected' + selectedBitstreams: [] + }, +}; diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts new file mode 100644 index 0000000000..3eeacfe1ce --- /dev/null +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@angular/core'; +import { AccessControlItem } from '../../../shared/access-control-array-form/access-control-array-form.component'; +import { Observable, of } from 'rxjs'; + +export interface AccessControlDropdownDataResponse { + id: string; + itemAccessConditionOptions: AccessControlItem[]; + bitstreamAccessConditionOptions: AccessControlItem[]; +} + +@Injectable() +export class ItemAccessControlService { + dropdownData$: Observable = of(accessControlDropdownData); + + + execute(payload: any) { + console.log('execute', payload); + + const blob = new Blob([JSON.stringify(payload, null, 2)], { + type: 'application/json', + }); + + const file = new File([blob], 'data.json', { + type: 'application/json', + }); + + const url = URL.createObjectURL(file); + window.open(url, '_blank'); + + } +} + +const accessControlDropdownData: AccessControlDropdownDataResponse = { + 'id': 'default', + 'itemAccessConditionOptions': [ + { + 'name': 'openaccess' + }, + { + 'name': 'administrator' + }, + { + 'name': 'embargo', + 'hasStartDate': true, + 'maxStartDate': '2018-06-24T00:40:54.970+0000' + }, + { + 'name': 'lease', + 'hasEndDate': true, + 'maxEndDate': '2017-12-24T00:40:54.970+0000' + } + ], + 'bitstreamAccessConditionOptions': [ + { + 'name': 'openaccess' + }, + { + 'name': 'administrator' + }, + { + 'name': 'embargo', + 'hasStartDate': true, + 'maxStartDate': '2018-06-24T00:40:54.970+0000' + }, + { + 'name': 'lease', + 'hasEndDate': true, + 'maxEndDate': '2017-12-24T00:40:54.970+0000' + } + ] +}; diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-array-form/access-control-array-form.component.html new file mode 100644 index 0000000000..dc8caf403c --- /dev/null +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.html @@ -0,0 +1,68 @@ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+ + + +
diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.scss b/src/app/shared/access-control-array-form/access-control-array-form.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.spec.ts b/src/app/shared/access-control-array-form/access-control-array-form.component.spec.ts new file mode 100644 index 0000000000..7c3ed06be1 --- /dev/null +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.spec.ts @@ -0,0 +1,101 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AccessControlArrayFormComponent, AccessControlItemValue } from './access-control-array-form.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { SharedBrowseByModule } from '../browse-by/shared-browse-by.module'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; +import { ControlMaxStartDatePipe } from './control-max-start-date.pipe'; +import { ControlMaxEndDatePipe } from './control-max-end-date.pipe'; +import { DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; + +fdescribe('AccessControlArrayFormComponent', () => { + let component: AccessControlArrayFormComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ CommonModule, ReactiveFormsModule, SharedBrowseByModule, TranslateModule, NgbDatepickerModule ], + declarations: [ AccessControlArrayFormComponent, ControlMaxStartDatePipe, ControlMaxEndDatePipe ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AccessControlArrayFormComponent); + component = fixture.componentInstance; + component.dropdownOptions = [{name: 'Option1'}, {name: 'Option2'}]; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have only one empty control access item in the form', () => { + const accessControlItems = fixture.debugElement.queryAll(By.css('[data-testId="access-control-item"]')); + expect(accessControlItems.length).toEqual(1); + }); + + it('should add access control item', () => { + component.addAccessControlItem(); + expect(component.accessControl.length).toEqual(2); + }); + + it('should remove access control item', () => { + component.removeAccessControlItem(0); + expect(component.accessControl.length).toEqual(0); + + component.addAccessControlItem(); + component.removeAccessControlItem(0); + expect(component.accessControl.length).toEqual(0); + }); + + it('should set access control item value', () => { + const item: AccessControlItemValue = { itemName: 'item1', startDate: '2022-01-01', endDate: '2022-02-01' }; + component.addAccessControlItem(item.itemName); + component.accessControl.controls[0].patchValue(item); + expect(component.form.value.accessControl[0]).toEqual(item); + }); + + it('should reset form value', () => { + const item: AccessControlItemValue = { itemName: 'item1', startDate: '2022-01-01', endDate: '2022-02-01' }; + component.addAccessControlItem(item.itemName); + component.accessControl.controls[1].patchValue(item); + component.reset(); + expect(component.form.value.accessControl[1].value).toEqual(undefined); + }); + + + it('should display a select dropdown with options', () => { + const selectElement: DebugElement = fixture.debugElement.query(By.css('select#accesscontroloption')); + expect(selectElement).toBeTruthy(); + + const options = selectElement.nativeElement.querySelectorAll('option'); + expect(options.length).toEqual(3); // 2 options + default empty option + + expect(options[0].value).toEqual(''); + expect(options[1].value).toEqual('Option1'); + expect(options[2].value).toEqual('Option2'); + }); + + it('should add new access control items when clicking "Add more" button', () => { + const addButton: DebugElement = fixture.debugElement.query(By.css('button#add-btn')); + addButton.nativeElement.click(); + fixture.detectChanges(); + + const accessControlItems = fixture.debugElement.queryAll(By.css('[data-testId="access-control-item"]')); + expect(accessControlItems.length).toEqual(2); + }); + + it('should remove access control items when clicking remove button', () => { + const removeButton: DebugElement = fixture.debugElement.query(By.css('button.btn-outline-danger')); + removeButton.nativeElement.click(); + fixture.detectChanges(); + + const accessControlItems = fixture.debugElement.queryAll(By.css('[data-testId="access-control-item"]')); + expect(accessControlItems.length).toEqual(0); + }); +}); diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-array-form/access-control-array-form.component.ts new file mode 100644 index 0000000000..00ff52c695 --- /dev/null +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.ts @@ -0,0 +1,88 @@ +import { Component, Input, NgModule, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormArray, FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { SharedBrowseByModule } from '../browse-by/shared-browse-by.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; +import { ControlMaxStartDatePipe } from './control-max-start-date.pipe'; +import { ControlMaxEndDatePipe } from './control-max-end-date.pipe'; + +// type of the dropdown item that comes from backend +export interface AccessControlItem { + name: string + hasStartDate?: boolean + maxStartDate?: string + hasEndDate?: boolean + maxEndDate?: string +} + +// will be used on the form value +export interface AccessControlItemValue { + itemName: string | null; // item name + startDate?: string; + endDate?: string; +} + +export interface AccessControlArrayFormValue { + accessControl: AccessControlItemValue[]; +} + +@Component({ + selector: 'ds-access-control-array-form', + templateUrl: './access-control-array-form.component.html', + styleUrls: [ './access-control-array-form.component.scss' ] +}) +export class AccessControlArrayFormComponent implements OnInit { + @Input() dropdownOptions: AccessControlItem[] = []; + @Input() accessControlItems: AccessControlItemValue[] = []; + + form = this.fb.group({ + accessControl: this.fb.array([]) + }); + + constructor(private fb: FormBuilder) { + } + + ngOnInit(): void { + if (this.accessControlItems.length === 0) { + this.addAccessControlItem(); + } else { + for (const item of this.accessControlItems) { + this.addAccessControlItem(item.itemName); + } + } + } + + get accessControl() { + return this.form.get('accessControl') as FormArray; + } + + addAccessControlItem(itemName: string = null) { + this.accessControl.push(this.fb.group({ + itemName, + startDate: null, + endDate: null + })); + } + + removeAccessControlItem(index: number) { + this.accessControl.removeAt(index); + } + + getValue() { + return this.form.value; + } + + reset() { + this.accessControl.reset([]); + } + +} + +@NgModule({ + imports: [ CommonModule, ReactiveFormsModule, SharedBrowseByModule, TranslateModule, NgbDatepickerModule ], + declarations: [ AccessControlArrayFormComponent, ControlMaxStartDatePipe, ControlMaxEndDatePipe ], + exports: [ AccessControlArrayFormComponent ], +}) +export class AccessControlArrayFormModule { +} diff --git a/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts b/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts new file mode 100644 index 0000000000..0ddff2a042 --- /dev/null +++ b/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { AbstractControl } from '@angular/forms'; +import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap/datepicker/ngb-date-struct'; +import { AccessControlItem } from './access-control-array-form.component'; + +@Pipe({ + // eslint-disable-next-line @angular-eslint/pipe-prefix + name: 'maxEndDate', + pure: false +}) +export class ControlMaxEndDatePipe implements PipeTransform { + transform(control: AbstractControl, dropdownOptions: AccessControlItem[]): NgbDateStruct | null { + const { itemName } = control.value; + const item = dropdownOptions.find((x) => x.name === itemName); + if (!item?.hasEndDate) { + return null; + } + const date = new Date(item.maxEndDate); + return { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate() + } as NgbDateStruct; + } + +} diff --git a/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts b/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts new file mode 100644 index 0000000000..01a9607a16 --- /dev/null +++ b/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { AbstractControl } from '@angular/forms'; +import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap/datepicker/ngb-date-struct'; +import { AccessControlItem } from './access-control-array-form.component'; + +@Pipe({ + // eslint-disable-next-line @angular-eslint/pipe-prefix + name: 'maxStartDate', + pure: false +}) +export class ControlMaxStartDatePipe implements PipeTransform { + transform(control: AbstractControl, dropdownOptions: AccessControlItem[]): NgbDateStruct | null { + const { itemName } = control.value; + const item = dropdownOptions.find((x) => x.name === itemName); + if (!item?.hasStartDate) { + return null; + } + const date = new Date(item.maxStartDate); + return { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate() + } as NgbDateStruct; + } + +} From 3a45ecf5784bc622b993baf80fc7b5c4d7cca762 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 3 May 2023 09:31:01 +0200 Subject: [PATCH 03/45] [CST-9636] Added bulk access condition options service --- .../community-access-control.service.ts | 23 ++++++---- .../bulk-access-condition-options.service.ts | 33 ++++++++++++++ .../bulk-access-condition-options.model.ts | 45 +++++++++++++++++++ .../access-control-array-form.component.ts | 13 +----- 4 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 src/app/core/data/bulk-access-condition-options.service.ts create mode 100644 src/app/core/shared/bulk-access-condition-options.model.ts diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts index d78d42ba90..89534683b7 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts @@ -1,19 +1,24 @@ import { Injectable } from '@angular/core'; -import { AccessControlItem } from '../../../shared/access-control-array-form/access-control-array-form.component'; -import { Observable, of } from 'rxjs'; +import { of } from 'rxjs'; +import { BulkAccessConditionOptionsService } from '../../../core/data/bulk-access-condition-options.service'; +import { BulkAccessConditionOptions } from '../../../core/shared/bulk-access-condition-options.model'; -export interface AccessControlDropdownDataResponse { - id: string; - itemAccessConditionOptions: AccessControlItem[]; - bitstreamAccessConditionOptions: AccessControlItem[]; -} @Injectable() export class CommunityAccessControlService { - dropdownData$: Observable = of(accessControlDropdownData); + constructor(private service: BulkAccessConditionOptionsService) {} + + dropdownData$ = of(accessControlDropdownData); + + // dropdownData$ = this.service.getAll().pipe( + // getAllSucceededRemoteData(), + // filter((data) => data.hasSucceeded), + // map((data) => data.payload) + // ); } -const accessControlDropdownData: AccessControlDropdownDataResponse = { +const accessControlDropdownData: BulkAccessConditionOptions = { + _links: { self: undefined }, type: undefined, uuid: '', 'id': 'default', 'itemAccessConditionOptions': [ { diff --git a/src/app/core/data/bulk-access-condition-options.service.ts b/src/app/core/data/bulk-access-condition-options.service.ts new file mode 100644 index 0000000000..bfff0a8dd6 --- /dev/null +++ b/src/app/core/data/bulk-access-condition-options.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RemoteData } from './remote-data'; +import { RequestService } from './request.service'; +import { IdentifiableDataService } from './base/identifiable-data.service'; +import { BulkAccessConditionOptions } from '../shared/bulk-access-condition-options.model'; + +@Injectable() +/** + * Data Service responsible for retrieving Bulk Access Condition Options from the REST API + */ +export class BulkAccessConditionOptionsService extends IdentifiableDataService { + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + ) { + super('bulkaccessconditionoptions', requestService, rdbService, objectCache, halService); + } + + getAll(): Observable> { + return this.findByHref(this.halService.getEndpoint(this.linkPath)); + } + + // findByPropertyName(name: string): Observable> { + // return this.findById(name); + // } +} diff --git a/src/app/core/shared/bulk-access-condition-options.model.ts b/src/app/core/shared/bulk-access-condition-options.model.ts new file mode 100644 index 0000000000..a1c8fe0702 --- /dev/null +++ b/src/app/core/shared/bulk-access-condition-options.model.ts @@ -0,0 +1,45 @@ +import { autoserialize, autoserializeAs, deserialize } from 'cerialize'; +import { typedObject } from '../cache/builders/build-decorators'; +import { excludeFromEquals } from '../utilities/equals.decorators'; +import { ResourceType } from './resource-type'; +import { CacheableObject } from '../cache/cacheable-object.model'; +import { HALLink } from './hal-link.model'; + +export const BULK_ACCESS_CONDITION_OPTIONS = new ResourceType('bulkAccessConditionOptions'); + +/** + * Model class for a bulk access condition options + */ +@typedObject +export class BulkAccessConditionOptions implements CacheableObject { + static type = BULK_ACCESS_CONDITION_OPTIONS; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + @autoserializeAs(String, 'name') + uuid: string; + + @autoserialize + id: string; + + @deserialize + itemAccessConditionOptions: AccessControlItem[]; + + @deserialize + bitstreamAccessConditionOptions: AccessControlItem[]; + + _links: { self: HALLink }; +} + +export interface AccessControlItem { + name: string + hasStartDate?: boolean + maxStartDate?: string + hasEndDate?: boolean + maxEndDate?: string +} diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-array-form/access-control-array-form.component.ts index 00ff52c695..bde7d30be8 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.ts @@ -6,15 +6,8 @@ import { TranslateModule } from '@ngx-translate/core'; import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; import { ControlMaxStartDatePipe } from './control-max-start-date.pipe'; import { ControlMaxEndDatePipe } from './control-max-end-date.pipe'; +import { AccessControlItem } from '../../core/shared/bulk-access-condition-options.model'; -// type of the dropdown item that comes from backend -export interface AccessControlItem { - name: string - hasStartDate?: boolean - maxStartDate?: string - hasEndDate?: boolean - maxEndDate?: string -} // will be used on the form value export interface AccessControlItemValue { @@ -23,10 +16,6 @@ export interface AccessControlItemValue { endDate?: string; } -export interface AccessControlArrayFormValue { - accessControl: AccessControlItemValue[]; -} - @Component({ selector: 'ds-access-control-array-form', templateUrl: './access-control-array-form.component.html', From 301863fb807563941ccd443a97faa824cb949505 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Tue, 9 May 2023 12:42:08 +0200 Subject: [PATCH 04/45] [CST-9636] Added translations and validations to item, community and collection access control --- .../collection-access-control.component.html | 74 +++++++++---- .../collection-access-control.component.ts | 8 ++ .../collection-access-control.service.ts | 2 +- .../community-access-control.component.html | 74 +++++++++---- .../community-access-control.component.ts | 7 ++ .../bulk-access-condition-options.service.ts | 2 +- .../item-access-control.component.html | 101 ++++++++++++------ .../item-access-control.component.ts | 10 +- .../item-access-control.service.ts | 5 +- .../access-control-array-form.component.html | 16 ++- .../access-control-array-form.component.ts | 70 ++++++++++-- .../control-max-end-date.pipe.ts | 2 +- .../control-max-start-date.pipe.ts | 2 +- src/assets/i18n/en.json5 | 20 ++++ 14 files changed, 296 insertions(+), 97 deletions(-) diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html index 52eda57f2c..ac07f30f84 100644 --- a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html @@ -1,39 +1,51 @@
-

- This form allows you to perform changes to the access condition of all the items owned by collection under this community. - Changes can be performed on the access condition for the metadata (item) or for the content (bitstream). -

+

{{'collection-access-control-title' | translate}}

-

Item's Metadata

- +

+ {{ 'access-control-item-header-toggle' | translate }} +

+ +
-
Mode
+
+ {{'access-control-mode' | translate}} +
- + name="itemMode" id="itemReplace" value="replace" + [disabled]="!state.item.toggleStatus" + [(ngModel)]="state.item.accessMode"> +
- + name="itemMode" id="itemAdd" value="add" + [disabled]="!state.item.toggleStatus" + [(ngModel)]="state.item.accessMode"> +
-
Access conditions
+
{{'access-control-access-conditions' | translate}}
- You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. + {{'access-control-no-access-conditions-warning-message' | translate}}
@@ -45,31 +57,43 @@
-

Bitstreams

+

+ {{ 'access-control-bitstream-header-toggle' | translate }} +

-
Mode
+
+ {{'access-control-mode' | translate}} +
- + name="bitstreamMode" id="bitstreamReplace" value="replace" + [disabled]="!state.bitstream.toggleStatus" + [(ngModel)]="state.bitstream.accessMode"> +
- + name="bitstreamMode" id="bitstreamAdd" value="add" + [disabled]="!state.bitstream.toggleStatus" + [(ngModel)]="state.bitstream.accessMode"> +
-
Access conditions
+
{{'access-control-access-conditions' | translate}}
- You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. + {{'access-control-no-access-conditions-warning-message' | translate}}
@@ -84,8 +108,12 @@
- - + +
diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts index 0791e29233..35ba6076f9 100644 --- a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts @@ -42,6 +42,14 @@ export class CollectionAccessControlComponent implements OnInit { console.log('itemAccess', itemAccess); } + handleStatusChange(type: 'item' | 'bitstream', active: boolean) { + if (type === 'bitstream') { + active ? this.bitstreamAccessCmp.enable() : this.bitstreamAccessCmp.disable(); + } else if (type === 'item') { + active ? this.itemAccessCmp.enable() : this.itemAccessCmp.disable(); + } + } + } const initialState = { diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts index 9cc7b8d85a..3267461fe6 100644 --- a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { AccessControlItem } from '../../../shared/access-control-array-form/access-control-array-form.component'; +import { AccessControlItem } from 'src/app/core/shared/bulk-access-condition-options.model'; import { Observable, of } from 'rxjs'; export interface AccessControlDropdownDataResponse { diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html index 52eda57f2c..26f394620a 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html @@ -1,39 +1,51 @@
-

- This form allows you to perform changes to the access condition of all the items owned by collection under this community. - Changes can be performed on the access condition for the metadata (item) or for the content (bitstream). -

+

{{ 'community-access-control-title' | translate }}

-

Item's Metadata

- +

+ {{ 'access-control-item-header-toggle' | translate }} +

+ +
-
Mode
+
+ {{'access-control-mode' | translate}} +
- + name="itemMode" id="itemReplace" value="replace" + [disabled]="!state.item.toggleStatus" + [(ngModel)]="state.item.accessMode"> +
- + name="itemMode" id="itemAdd" value="add" + [disabled]="!state.item.toggleStatus" + [(ngModel)]="state.item.accessMode"> +
-
Access conditions
+
{{'access-control-access-conditions' | translate}}
- You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. + {{'access-control-no-access-conditions-warning-message' | translate}}
@@ -45,31 +57,43 @@
-

Bitstreams

+

+ {{ 'access-control-bitstream-header-toggle' | translate }} +

-
Mode
+
+ {{'access-control-mode' | translate}} +
- + name="bitstreamMode" id="bitstreamReplace" value="replace" + [disabled]="!state.bitstream.toggleStatus" + [(ngModel)]="state.bitstream.accessMode"> +
- + name="bitstreamMode" id="bitstreamAdd" value="add" + [disabled]="!state.bitstream.toggleStatus" + [(ngModel)]="state.bitstream.accessMode"> +
-
Access conditions
+
{{'access-control-access-conditions' | translate}}
- You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. + {{'access-control-no-access-conditions-warning-message' | translate}}
@@ -84,8 +108,12 @@
- - + +
diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts index 202c04b7b5..a25adb177c 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts @@ -42,6 +42,13 @@ export class CommunityAccessControlComponent implements OnInit { console.log('itemAccess', itemAccess); } + handleStatusChange(type: 'item' | 'bitstream', active: boolean) { + if (type === 'bitstream') { + active ? this.bitstreamAccessCmp.enable() : this.bitstreamAccessCmp.disable(); + } else if (type === 'item') { + active ? this.itemAccessCmp.enable() : this.itemAccessCmp.disable(); + } + } } const initialState = { diff --git a/src/app/core/data/bulk-access-condition-options.service.ts b/src/app/core/data/bulk-access-condition-options.service.ts index bfff0a8dd6..30e0402dbf 100644 --- a/src/app/core/data/bulk-access-condition-options.service.ts +++ b/src/app/core/data/bulk-access-condition-options.service.ts @@ -8,7 +8,7 @@ import { RequestService } from './request.service'; import { IdentifiableDataService } from './base/identifiable-data.service'; import { BulkAccessConditionOptions } from '../shared/bulk-access-condition-options.model'; -@Injectable() +@Injectable({ providedIn: 'root' }) /** * Data Service responsible for retrieving Bulk Access Condition Options from the REST API */ diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html index f5a02b0452..4e72d0905f 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html @@ -1,39 +1,50 @@
-

- This form allows you to perform changes to the access condition of all the items owned by collection under this community. - Changes can be performed on the access condition for the metadata (item) or for the content (bitstream). -

+

{{ 'item-access-control-title' | translate }}

-

Item's Metadata

- +

+ {{ 'access-control-item-header-toggle' | translate }} +

+ +
-
Mode
+
+ {{ 'access-control-mode' | translate }} +
- + name="itemMode" id="itemReplace" value="replace" + [disabled]="!state.item.toggleStatus" + [(ngModel)]="state.item.accessMode"> +
- + name="itemMode" id="itemAdd" value="add" + [disabled]="!state.item.toggleStatus" + [(ngModel)]="state.item.accessMode"> +
-
Access conditions
- +
{{'access-control-access-conditions' | translate}}
- You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. + {{'access-control-no-access-conditions-warning-message' | translate}}
@@ -45,49 +56,75 @@
-

Bitstreams

- +

+ {{'access-control-bitstream-header-toggle' | translate}} +

+ +
-
Limit the changes to specific bitstreams
+
+ {{'access-control-limit-to-specific' | translate}} +
- + name="changesLimit" id="processAll" value="all" + [disabled]="!state.bitstream.toggleStatus" + [(ngModel)]="state.bitstream.changesLimit"> +
+ name="changesLimit" id="processSelected" value="selected" + [disabled]="!state.bitstream.toggleStatus" + [(ngModel)]="state.bitstream.changesLimit">
-
Mode
+
+ {{'access-control-mode' | translate}} +
- + name="bitstreamMode" id="bitstreamReplace" value="replace" + [disabled]="!state.bitstream.toggleStatus" + [(ngModel)]="state.bitstream.accessMode"> +
- + name="bitstreamMode" id="bitstreamAdd" value="add" + [disabled]="!state.bitstream.toggleStatus" + [(ngModel)]="state.bitstream.accessMode"> +
-
Access conditions
+
+ {{'access-control-access-conditions' | translate}} +
- You have not specified any access conditions, the new items access conditions will be inherited from the owning collection. + {{'access-control-no-access-conditions-warning-message' | translate}}
@@ -102,8 +139,12 @@
- - + +
diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts index 6882dfb2a1..5bd614537a 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts @@ -2,9 +2,6 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { AccessControlArrayFormComponent } from '../../../shared/access-control-array-form/access-control-array-form.component'; -import { - CollectionAccessControlService -} from '../../../collection-page/edit-collection-page/collection-access-control/collection-access-control.service'; import { shareReplay } from 'rxjs'; import { ItemAccessControlService } from './item-access-control.service'; @@ -48,6 +45,13 @@ export class ItemAccessControlComponent implements OnInit { }); } + handleStatusChange(type: 'item' | 'bitstream', active: boolean) { + if (type === 'bitstream') { + active ? this.bitstreamAccessCmp.enable() : this.bitstreamAccessCmp.disable(); + } else if (type === 'item') { + active ? this.itemAccessCmp.enable() : this.itemAccessCmp.disable(); + } + } } const initialState = { diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts index 3eeacfe1ce..57b28f81b0 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { AccessControlItem } from '../../../shared/access-control-array-form/access-control-array-form.component'; import { Observable, of } from 'rxjs'; +import { AccessControlItem } from '../../../core/shared/bulk-access-condition-options.model'; export interface AccessControlDropdownDataResponse { id: string; @@ -26,7 +26,6 @@ export class ItemAccessControlService { const url = URL.createObjectURL(file); window.open(url, '_blank'); - } } @@ -42,7 +41,7 @@ const accessControlDropdownData: AccessControlDropdownDataResponse = { { 'name': 'embargo', 'hasStartDate': true, - 'maxStartDate': '2018-06-24T00:40:54.970+0000' + 'maxStartDate': '2023-05-12T00:40:54.970+0000' }, { 'name': 'lease', diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-array-form/access-control-array-form.component.html index dc8caf403c..24dcc11120 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.html @@ -25,7 +25,9 @@ #d="ngbDatepicker" />
-
@@ -43,15 +45,18 @@ #d1="ngbDatepicker" />
-
-
@@ -60,9 +65,10 @@ diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-array-form/access-control-array-form.component.ts index bde7d30be8..ace507a3a6 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.ts @@ -1,12 +1,14 @@ -import { Component, Input, NgModule, OnInit } from '@angular/core'; +import { Component, Input, NgModule, OnDestroy, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormArray, FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { FormArray, FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms'; import { SharedBrowseByModule } from '../browse-by/shared-browse-by.module'; import { TranslateModule } from '@ngx-translate/core'; import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; import { ControlMaxStartDatePipe } from './control-max-start-date.pipe'; import { ControlMaxEndDatePipe } from './control-max-end-date.pipe'; import { AccessControlItem } from '../../core/shared/bulk-access-condition-options.model'; +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; // will be used on the form value @@ -19,12 +21,15 @@ export interface AccessControlItemValue { @Component({ selector: 'ds-access-control-array-form', templateUrl: './access-control-array-form.component.html', - styleUrls: [ './access-control-array-form.component.scss' ] + styleUrls: [ './access-control-array-form.component.scss' ], + exportAs: 'accessControlArrayForm' }) -export class AccessControlArrayFormComponent implements OnInit { +export class AccessControlArrayFormComponent implements OnInit, OnDestroy { @Input() dropdownOptions: AccessControlItem[] = []; @Input() accessControlItems: AccessControlItemValue[] = []; + private destroy$ = new Subject(); + form = this.fb.group({ accessControl: this.fb.array([]) }); @@ -40,6 +45,34 @@ export class AccessControlArrayFormComponent implements OnInit { this.addAccessControlItem(item.itemName); } } + + this.accessControl.valueChanges + .pipe( + distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), + takeUntil(this.destroy$) + ) + .subscribe((value) => { + for (const [ index, controlValue ] of value.entries()) { + if (controlValue.itemName) { + const item = this.dropdownOptions.find((x) => x.name === controlValue.itemName); + const startDateCtrl = this.accessControl.controls[index].get('startDate'); + const endDateCtrl = this.accessControl.controls[index].get('endDate'); + + if (item?.hasStartDate) { + startDateCtrl.enable({ emitEvent: false }); + } else { + startDateCtrl.patchValue(null); + startDateCtrl.disable({ emitEvent: false }); + } + if (item?.hasEndDate) { + endDateCtrl.enable({ emitEvent: false }); + } else { + endDateCtrl.patchValue(null); + endDateCtrl.disable({ emitEvent: false }); + } + } + } + }); } get accessControl() { @@ -49,8 +82,8 @@ export class AccessControlArrayFormComponent implements OnInit { addAccessControlItem(itemName: string = null) { this.accessControl.push(this.fb.group({ itemName, - startDate: null, - endDate: null + startDate: new FormControl({ value: null, disabled: true }), + endDate: new FormControl({ value: null, disabled: true }) })); } @@ -66,6 +99,31 @@ export class AccessControlArrayFormComponent implements OnInit { this.accessControl.reset([]); } + disable() { + this.form.disable(); + + // disable all date controls + for (const control of this.accessControl.controls) { + control.get('startDate').disable(); + control.get('endDate').disable(); + } + } + + enable() { + this.form.enable(); + + // enable date controls + for (const control of this.accessControl.controls) { + control.get('startDate').enable(); + control.get('endDate').enable(); + } + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + } @NgModule({ diff --git a/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts b/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts index 0ddff2a042..810f3e71d0 100644 --- a/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts +++ b/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts @@ -1,7 +1,7 @@ import { Pipe, PipeTransform } from '@angular/core'; import { AbstractControl } from '@angular/forms'; import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap/datepicker/ngb-date-struct'; -import { AccessControlItem } from './access-control-array-form.component'; +import { AccessControlItem } from 'src/app/core/shared/bulk-access-condition-options.model'; @Pipe({ // eslint-disable-next-line @angular-eslint/pipe-prefix diff --git a/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts b/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts index 01a9607a16..458cb36465 100644 --- a/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts +++ b/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts @@ -1,7 +1,7 @@ import { Pipe, PipeTransform } from '@angular/core'; import { AbstractControl } from '@angular/forms'; import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap/datepicker/ngb-date-struct'; -import { AccessControlItem } from './access-control-array-form.component'; +import { AccessControlItem } from 'src/app/core/shared/bulk-access-condition-options.model'; @Pipe({ // eslint-disable-next-line @angular-eslint/pipe-prefix diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 2c7ae5459f..6541848df2 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5311,4 +5311,24 @@ "admin.system-wide-alert.breadcrumbs": "System-wide Alerts", "admin.system-wide-alert.title": "System-wide Alerts", + + "item-access-control-title": "This form allows you to perform changes to the access condition of all the items owned by collection under this community. Changes can be performed on the access condition for the metadata (item) or for the content (bitstream).", + "collection-access-control-title": "This form allows you to perform changes to the access condition of all the items owned by collection under this community. Changes can be performed on the access condition for the metadata (item) or for the content (bitstream).", + "community-access-control-title": "This form allows you to perform changes to the access condition of all the items owned by collection under this community. Changes can be performed on the access condition for the metadata (item) or for the content (bitstream).", + "access-control-item-header-toggle": "Item's Metadata", + "access-control-bitstream-header-toggle": "Bitstreams", + "access-control-mode": "Mode", + "access-control-access-conditions": "Access conditions", + "access-control-no-access-conditions-warning-message": "You have not specified any access conditions, the new items access conditions will be inherited from the owning collection.", + "access-control-replace-all": "Replace access conditions", + "access-control-add-to-existing": "Add to existing ones", + "access-control-limit-to-specific": "Limit the changes to specific bitstreams", + "access-control-process-all-bitstreams": "process all the bitstreams in the item", + "access-control-bitstreams-selected": "bitstreams selected", + "access-control-reset": "Reset", + "access-control-execute": "Execute", + "access-control-add-more": "Add more" + + + } From a088641e5cee0c561b2fb00ffa968554a5d69150 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Tue, 9 May 2023 16:41:40 +0200 Subject: [PATCH 05/45] [CST-9636] Added select bitstreams modal and made it work --- src/app/core/shared/context.model.ts | 1 + .../edit-item-page/edit-item-page.module.ts | 8 ++- ...rol-select-bitstreams-modal.component.html | 33 ++++++++++ ...rol-select-bitstreams-modal.component.scss | 0 ...-select-bitstreams-modal.component.spec.ts | 25 ++++++++ ...ntrol-select-bitstreams-modal.component.ts | 62 +++++++++++++++++++ .../item-access-control.component.html | 8 +++ .../item-access-control.component.ts | 56 ++++++++++++++--- .../bitstream-list-item.component.html | 0 .../bitstream-list-item.component.scss | 0 .../bitstream-list-item.component.spec.ts | 25 ++++++++ .../bitstream-list-item.component.ts | 17 +++++ .../themed-object-list.component.ts | 2 +- src/app/shared/shared.module.ts | 2 + 14 files changed, 229 insertions(+), 10 deletions(-) create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.scss create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.spec.ts create mode 100644 src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts create mode 100644 src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.html create mode 100644 src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.scss create mode 100644 src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.spec.ts create mode 100644 src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.ts diff --git a/src/app/core/shared/context.model.ts b/src/app/core/shared/context.model.ts index dbe5a64552..acc3313bb7 100644 --- a/src/app/core/shared/context.model.ts +++ b/src/app/core/shared/context.model.ts @@ -16,4 +16,5 @@ export enum Context { AdminWorkflowSearch = 'adminWorkflowSearch', SideBarSearchModal = 'sideBarSearchModal', SideBarSearchModalCurrent = 'sideBarSearchModalCurrent', + Bitstream = 'bitstream', } diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 65862b3de8..fb46697c7d 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -43,6 +43,10 @@ import { AccessControlArrayFormModule } from '../../shared/access-control-array-form/access-control-array-form.component'; import { UiSwitchModule } from 'ngx-ui-switch'; +import { + ItemAccessControlSelectBitstreamsModalComponent +} from './item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; +import { ResultsBackButtonModule } from '../../shared/results-back-button/results-back-button.module'; /** @@ -62,6 +66,7 @@ import { UiSwitchModule } from 'ngx-ui-switch'; DsoSharedModule, AccessControlArrayFormModule, UiSwitchModule, + ResultsBackButtonModule, ], declarations: [ EditItemPageComponent, @@ -89,7 +94,8 @@ import { UiSwitchModule } from 'ngx-ui-switch'; ItemAuthorizationsComponent, IdentifierDataComponent, ItemRegisterDoiComponent, - ItemAccessControlComponent + ItemAccessControlComponent, + ItemAccessControlSelectBitstreamsModalComponent ], providers: [ BundleDataService, diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html b/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html new file mode 100644 index 0000000000..e4159f3505 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html @@ -0,0 +1,33 @@ + + + diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.scss b/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.spec.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.spec.ts new file mode 100644 index 0000000000..7716bda735 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ItemAccessControlSelectBitstreamsModalComponent } from './item-access-control-select-bitstreams-modal.component'; + +describe('ItemAccessControlSelectBitstreamsModalComponent', () => { + let component: ItemAccessControlSelectBitstreamsModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ItemAccessControlSelectBitstreamsModalComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemAccessControlSelectBitstreamsModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts new file mode 100644 index 0000000000..dbae803c44 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts @@ -0,0 +1,62 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { TranslateService } from '@ngx-translate/core'; +import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; +import { BehaviorSubject } from 'rxjs'; +import { Item } from '../../../../core/shared/item.model'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { PaginatedList } from '../../../../core/data/paginated-list.model'; +import { Bitstream } from '../../../../core/shared/bitstream.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { hasValue } from '../../../../shared/empty.util'; +import { Context } from '../../../../core/shared/context.model'; + +export const ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID = 'item-access-control-select-bitstreams' + +@Component({ + selector: 'ds-item-access-control-select-bitstreams-modal', + templateUrl: './item-access-control-select-bitstreams-modal.component.html', + styleUrls: [ './item-access-control-select-bitstreams-modal.component.scss' ] +}) +export class ItemAccessControlSelectBitstreamsModalComponent implements OnInit { + + LIST_ID = ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID; + + @Input() item!: Item; + @Input() selectedBitstreams: string[] = []; + + data$ = new BehaviorSubject> | null>(null); + paginationConfig: PaginationComponentOptions; + pageSize = 5; + + context: Context = Context.Bitstream; + + constructor( + private bitstreamService: BitstreamDataService, + protected paginationService: PaginationService, + protected translateService: TranslateService, + public activeModal: NgbActiveModal + ) { } + + ngOnInit() { + this.loadForPage(1); + + this.paginationConfig = new PaginationComponentOptions(); + this.paginationConfig.id = 'iacsbm'; + this.paginationConfig.currentPage = 1; + if (hasValue(this.pageSize)) { + this.paginationConfig.pageSize = this.pageSize; + } + } + + loadForPage(page: number) { + this.bitstreamService.findAllByItemAndBundleName(this.item, 'ORIGINAL', { currentPage: page}, false) + .pipe( + getFirstCompletedRemoteData(), + ) + .subscribe(this.data$); + } + +} diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html index 4e72d0905f..6b65f2fe8b 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html @@ -87,6 +87,14 @@
diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts index 5bd614537a..29f1d55ada 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts @@ -1,22 +1,42 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { AccessControlArrayFormComponent } from '../../../shared/access-control-array-form/access-control-array-form.component'; -import { shareReplay } from 'rxjs'; +import { concatMap, Observable, shareReplay } from 'rxjs'; import { ItemAccessControlService } from './item-access-control.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { + ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID, + ItemAccessControlSelectBitstreamsModalComponent +} from './item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; +import { map, take } from 'rxjs/operators'; +import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Item } from '../../../core/shared/item.model'; +import { ActivatedRoute } from '@angular/router'; +import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; +import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; @Component({ selector: 'ds-item-access-control', templateUrl: './item-access-control.component.html', - styleUrls: ['./item-access-control.component.scss'], - providers: [ItemAccessControlService] + styleUrls: [ './item-access-control.component.scss' ], + providers: [ ItemAccessControlService ] }) -export class ItemAccessControlComponent implements OnInit { +export class ItemAccessControlComponent implements OnInit, OnDestroy { + + itemRD$: Observable>; @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; - constructor(private itemAccessControlService: ItemAccessControlService) {} + constructor( + private itemAccessControlService: ItemAccessControlService, + private selectableListService: SelectableListService, + protected modalService: NgbModal, + private route: ActivatedRoute, + private cdr: ChangeDetectorRef + ) {} state = initialState; @@ -25,7 +45,9 @@ export class ItemAccessControlComponent implements OnInit { ); ngOnInit(): void { - + this.itemRD$ = this.route.parent.parent.data.pipe( + map((data) => data.dso) + ).pipe(getFirstSucceededRemoteData()) as Observable>; } reset() { @@ -52,6 +74,24 @@ export class ItemAccessControlComponent implements OnInit { active ? this.itemAccessCmp.enable() : this.itemAccessCmp.disable(); } } + + openSelectBitstreamsModal(item: Item) { + const ref = this.modalService.open(ItemAccessControlSelectBitstreamsModalComponent); + ref.componentInstance.selectedBitstreams = this.state.bitstream.selectedBitstreams; + ref.componentInstance.item = item; + + ref.closed.pipe( + concatMap(() => this.selectableListService.getSelectableList(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID)), + take(1) + ).subscribe((list) => { + this.state.bitstream.selectedBitstreams = list.selection; + this.cdr.detectChanges(); + }); + } + + ngOnDestroy(): void { + this.selectableListService.deselectAll(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID); + } } const initialState = { @@ -63,6 +103,6 @@ const initialState = { toggleStatus: false, accessMode: '', changesLimit: '', // 'all' | 'selected' - selectedBitstreams: [] + selectedBitstreams: [] as ListableObject[], }, }; diff --git a/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.html b/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.scss b/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.spec.ts b/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.spec.ts new file mode 100644 index 0000000000..25d6a56295 --- /dev/null +++ b/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BitstreamListItemComponent } from './bitstream-list-item.component'; + +describe('BitstreamListItemComponent', () => { + let component: BitstreamListItemComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BitstreamListItemComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamListItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.ts b/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.ts new file mode 100644 index 0000000000..f281c0bd77 --- /dev/null +++ b/src/app/shared/object-list/bitstream-list-item/bitstream-list-item.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { listableObjectComponent } from '../../object-collection/shared/listable-object/listable-object.decorator'; +import { ViewMode } from '../../../core/shared/view-mode.model'; +import { + AbstractListableElementComponent +} from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; +import { Bitstream } from '../../../core/shared/bitstream.model'; +import { Context } from '../../../core/shared/context.model'; + + +@listableObjectComponent(Bitstream, ViewMode.ListElement, Context.Bitstream) +@Component({ + selector: 'ds-bitstream-list-item', + template: ` {{object.name}} `, + styleUrls: ['./bitstream-list-item.component.scss'] +}) +export class BitstreamListItemComponent extends AbstractListableElementComponent{} diff --git a/src/app/shared/object-list/themed-object-list.component.ts b/src/app/shared/object-list/themed-object-list.component.ts index 913302351f..8e2280a163 100644 --- a/src/app/shared/object-list/themed-object-list.component.ts +++ b/src/app/shared/object-list/themed-object-list.component.ts @@ -48,7 +48,7 @@ export class ThemedObjectListComponent extends ThemedComponent Date: Tue, 22 Nov 2022 14:59:40 +0100 Subject: [PATCH 06/45] [CST-9636] Add flag to show or not thumbnail along list element by component's input firstly --- ...ue-search-result-list-element.component.ts | 10 --------- ...me-search-result-list-element.component.ts | 10 --------- ...al-search-result-list-element.component.ts | 10 --------- ...it-search-result-list-element.component.ts | 10 --------- ...on-search-result-list-element.component.ts | 22 +------------------ ...ct-search-result-list-element.component.ts | 10 --------- .../object-collection.component.html | 3 +++ .../object-collection.component.ts | 5 +++++ ...table-object-component-loader.component.ts | 6 +++++ .../abstract-listable-element.component.ts | 5 +++++ .../object-detail.component.html | 1 + .../object-detail/object-detail.component.ts | 5 +++++ .../object-grid/object-grid.component.html | 6 ++++- .../object-grid/object-grid.component.ts | 12 +++++++--- .../object-list/object-list.component.html | 9 ++++---- .../object-list/object-list.component.ts | 5 +++++ ...em-search-result-list-element.component.ts | 7 +----- .../themed-object-list.component.ts | 6 +++++ .../search-results.component.html | 1 + .../search-results.component.ts | 5 +++++ .../themed-search-results.component.ts | 4 +++- src/app/shared/search/search.component.html | 1 + src/app/shared/search/search.component.ts | 5 +++++ .../shared/search/themed-search.component.ts | 4 +++- 24 files changed, 75 insertions(+), 87 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.ts index 88f93d9d85..d804006eb1 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.ts @@ -14,14 +14,4 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje */ export class JournalIssueSearchResultListElementComponent extends ItemSearchResultListElementComponent { - /** - * Display thumbnails if required by configuration - */ - showThumbnails: boolean; - - ngOnInit(): void { - super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; - } - } diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.ts index ec98946937..2a01d20668 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.ts @@ -14,14 +14,4 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje */ export class JournalVolumeSearchResultListElementComponent extends ItemSearchResultListElementComponent { - /** - * Display thumbnails if required by configuration - */ - showThumbnails: boolean; - - ngOnInit(): void { - super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; - } - } diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.ts index 199bd3a748..38150d1a6a 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.ts @@ -14,14 +14,4 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje */ export class JournalSearchResultListElementComponent extends ItemSearchResultListElementComponent { - /** - * Display thumbnails if required by configuration - */ - showThumbnails: boolean; - - ngOnInit(): void { - super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; - } - } diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.ts index baa27cdf0c..a9edb530cb 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.ts @@ -14,14 +14,4 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje */ export class OrgUnitSearchResultListElementComponent extends ItemSearchResultListElementComponent { - /** - * Display thumbnail if required by configuration - */ - showThumbnails: boolean; - - ngOnInit(): void { - super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; - } - } diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts index 217d7baef9..67c01ed8f7 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject } from '@angular/core'; +import { Component } from '@angular/core'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; @@ -6,9 +6,6 @@ import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { ItemSearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; -import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; -import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; -import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface'; @listableObjectComponent('PersonSearchResult', ViewMode.ListElement) @Component({ @@ -21,21 +18,4 @@ import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.inter */ export class PersonSearchResultListElementComponent extends ItemSearchResultListElementComponent { - public constructor( - protected truncatableService: TruncatableService, - protected dsoNameService: DSONameService, - @Inject(APP_CONFIG) protected appConfig: AppConfig - ) { - super(truncatableService, dsoNameService, appConfig); - } - - /** - * Display thumbnail if required by configuration - */ - showThumbnails: boolean; - - ngOnInit(): void { - super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; - } } diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.ts index 88e95528ac..aaf98a8091 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.ts @@ -14,14 +14,4 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje */ export class ProjectSearchResultListElementComponent extends ItemSearchResultListElementComponent { - /** - * Display thumbnail if required by configuration - */ - showThumbnails: boolean; - - ngOnInit(): void { - super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; - } - } diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index 3dafba8298..3b0dca80ac 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -8,6 +8,7 @@ [context]="context" [hidePaginationDetail]="hidePaginationDetail" [showPaginator]="showPaginator" + [showThumbnails]="showThumbnails" (paginationChange)="onPaginationChange($event)" (pageChange)="onPageChange($event)" (pageSizeChange)="onPageSizeChange($event)" @@ -34,6 +35,7 @@ [context]="context" [hidePaginationDetail]="hidePaginationDetail" [showPaginator]="showPaginator" + [showThumbnails]="showThumbnails" (paginationChange)="onPaginationChange($event)" (pageChange)="onPageChange($event)" (pageSizeChange)="onPageSizeChange($event)" @@ -50,6 +52,7 @@ [context]="context" [hidePaginationDetail]="hidePaginationDetail" [showPaginator]="showPaginator" + [showThumbnails]="showThumbnails" (contentChange)="contentChange.emit($event)" *ngIf="(currentMode$ | async) === viewModeEnum.DetailedListElement"> diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index 369192c488..e1f9182562 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -107,6 +107,11 @@ export class ObjectCollectionComponent implements OnInit { */ @Input() showPaginator = true; + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + /** * the page info of the list */ diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 6b75c59181..18b3d96d14 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -71,6 +71,11 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ @Input() showLabel = true; + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + /** * The value to display for this element */ @@ -127,6 +132,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges 'linkType', 'listID', 'showLabel', + 'showThumbnails', 'context', 'viewMode', 'value', diff --git a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts index 7d4e107b2b..d145b8146b 100644 --- a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts +++ b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts @@ -41,6 +41,11 @@ export class AbstractListableElementComponent { */ @Input() showLabel = true; + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + /** * The context we matched on to get this component */ diff --git a/src/app/shared/object-detail/object-detail.component.html b/src/app/shared/object-detail/object-detail.component.html index 05b8342ca3..d077e2fd2b 100644 --- a/src/app/shared/object-detail/object-detail.component.html +++ b/src/app/shared/object-detail/object-detail.component.html @@ -21,6 +21,7 @@
diff --git a/src/app/shared/object-detail/object-detail.component.ts b/src/app/shared/object-detail/object-detail.component.ts index 15bd5b7bca..1a32be74b8 100644 --- a/src/app/shared/object-detail/object-detail.component.ts +++ b/src/app/shared/object-detail/object-detail.component.ts @@ -64,6 +64,11 @@ export class ObjectDetailComponent { */ @Input() showPaginator = true; + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + /** * Emit when one of the listed object has changed. */ diff --git a/src/app/shared/object-grid/object-grid.component.html b/src/app/shared/object-grid/object-grid.component.html index 4050f93f77..59fe18820d 100644 --- a/src/app/shared/object-grid/object-grid.component.html +++ b/src/app/shared/object-grid/object-grid.component.html @@ -19,7 +19,11 @@
- +
diff --git a/src/app/shared/object-grid/object-grid.component.ts b/src/app/shared/object-grid/object-grid.component.ts index 1b5ab075e2..91630ca007 100644 --- a/src/app/shared/object-grid/object-grid.component.ts +++ b/src/app/shared/object-grid/object-grid.component.ts @@ -1,11 +1,12 @@ -import { combineLatest as observableCombineLatest, BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { startWith, distinctUntilChanged, map } from 'rxjs/operators'; +import { distinctUntilChanged, map, startWith } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, EventEmitter, - Input, OnInit, + Input, + OnInit, Output, ViewEncapsulation } from '@angular/core'; @@ -54,6 +55,11 @@ export class ObjectGridComponent implements OnInit { */ @Input() showPaginator = true; + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + /** * The whether or not the gear is hidden */ diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index b8712b85c5..8e0482b1f7 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -25,12 +25,13 @@ - diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index 65e2b508da..5161b75459 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -81,6 +81,11 @@ export class ObjectListComponent { */ @Input() showPaginator = true; + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + /** * Emit when one of the listed object has changed. */ diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts index f84ae642ad..99251d3c24 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts @@ -22,14 +22,9 @@ export class ItemSearchResultListElementComponent extends SearchResultListElemen */ itemPageRoute: string; - /** - * Display thumbnails if required by configuration - */ - showThumbnails: boolean; - ngOnInit(): void { super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; + this.showThumbnails = this.showThumbnails ?? this.appConfig.browseBy.showThumbnails; this.itemPageRoute = getItemPageRoute(this.dso); } } diff --git a/src/app/shared/object-list/themed-object-list.component.ts b/src/app/shared/object-list/themed-object-list.component.ts index 913302351f..4d7b5ca77b 100644 --- a/src/app/shared/object-list/themed-object-list.component.ts +++ b/src/app/shared/object-list/themed-object-list.component.ts @@ -78,6 +78,11 @@ export class ThemedObjectListComponent extends ThemedComponent diff --git a/src/app/shared/search/search-results/search-results.component.ts b/src/app/shared/search/search-results/search-results.component.ts index 0a83d3e5c6..15d2cc0f00 100644 --- a/src/app/shared/search/search-results/search-results.component.ts +++ b/src/app/shared/search/search-results/search-results.component.ts @@ -52,6 +52,11 @@ export class SearchResultsComponent { */ @Input() showCsvExport = false; + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + /** * The current sorting configuration of the search */ diff --git a/src/app/shared/search/search-results/themed-search-results.component.ts b/src/app/shared/search/search-results/themed-search-results.component.ts index 7abfb2dfa8..01ee5761f3 100644 --- a/src/app/shared/search/search-results/themed-search-results.component.ts +++ b/src/app/shared/search/search-results/themed-search-results.component.ts @@ -21,7 +21,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m templateUrl: '../../theme-support/themed.component.html', }) export class ThemedSearchResultsComponent extends ThemedComponent { - protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'showCsvExport', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'contentChange', 'deselectObject', 'selectObject']; + protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'showCsvExport', 'showThumbnails', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'contentChange', 'deselectObject', 'selectObject']; @Input() linkType: CollectionElementLinkType; @@ -31,6 +31,8 @@ export class ThemedSearchResultsComponent extends ThemedComponent diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index a5b9fb9c7d..5dd84344d4 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -128,6 +128,11 @@ export class SearchComponent implements OnInit { */ @Input() showSidebar = true; + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + /** * Whether to show the view mode switch */ diff --git a/src/app/shared/search/themed-search.component.ts b/src/app/shared/search/themed-search.component.ts index 44c22c8925..fe531e4f0f 100644 --- a/src/app/shared/search/themed-search.component.ts +++ b/src/app/shared/search/themed-search.component.ts @@ -19,7 +19,7 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode templateUrl: '../theme-support/themed.component.html', }) export class ThemedSearchComponent extends ThemedComponent { - protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showCsvExport', 'showSidebar', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics', 'query']; + protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showCsvExport', 'showSidebar', 'showThumbnails', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics', 'query']; @Input() configurationList: SearchConfigurationOption[]; @@ -51,6 +51,8 @@ export class ThemedSearchComponent extends ThemedComponent { @Input() showSidebar: boolean; + @Input() showThumbnails; + @Input() showViewModes: boolean; @Input() useUniquePageId: boolean; From 06fef61f02c156e4b22d0d590ee301421f08d2c4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 9 May 2023 18:39:04 +0200 Subject: [PATCH 07/45] [CST-9636] Create page for bulk access --- .../access-control-routing.module.ts | 20 +++++- .../access-control/access-control.module.ts | 24 +++++-- .../browse/bulk-access-browse.component.html | 51 ++++++++++++++ .../browse/bulk-access-browse.component.scss | 0 .../bulk-access-browse.component.spec.ts | 25 +++++++ .../browse/bulk-access-browse.component.ts | 67 +++++++++++++++++++ .../bulk-access/bulk-access.component.html | 9 +++ .../bulk-access/bulk-access.component.scss | 0 .../bulk-access/bulk-access.component.spec.ts | 25 +++++++ .../bulk-access/bulk-access.component.ts | 15 +++++ .../bulk-access-settings.component.html | 21 ++++++ .../bulk-access-settings.component.scss | 0 .../bulk-access-settings.component.spec.ts | 25 +++++++ .../bulk-access-settings.component.ts | 15 +++++ src/app/menu.resolver.ts | 11 +++ src/assets/i18n/en.json5 | 14 ++++ 16 files changed, 312 insertions(+), 10 deletions(-) create mode 100644 src/app/access-control/bulk-access/browse/bulk-access-browse.component.html create mode 100644 src/app/access-control/bulk-access/browse/bulk-access-browse.component.scss create mode 100644 src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts create mode 100644 src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts create mode 100644 src/app/access-control/bulk-access/bulk-access.component.html create mode 100644 src/app/access-control/bulk-access/bulk-access.component.scss create mode 100644 src/app/access-control/bulk-access/bulk-access.component.spec.ts create mode 100644 src/app/access-control/bulk-access/bulk-access.component.ts create mode 100644 src/app/access-control/bulk-access/settings/bulk-access-settings.component.html create mode 100644 src/app/access-control/bulk-access/settings/bulk-access-settings.component.scss create mode 100644 src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts create mode 100644 src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts diff --git a/src/app/access-control/access-control-routing.module.ts b/src/app/access-control/access-control-routing.module.ts index e64b0d170a..6f6de6cb26 100644 --- a/src/app/access-control/access-control-routing.module.ts +++ b/src/app/access-control/access-control-routing.module.ts @@ -6,8 +6,13 @@ import { GroupsRegistryComponent } from './group-registry/groups-registry.compon import { GROUP_EDIT_PATH } from './access-control-routing-paths'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { GroupPageGuard } from './group-registry/group-page.guard'; -import { GroupAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; -import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { + GroupAdministratorGuard +} from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; +import { + SiteAdministratorGuard +} from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { BulkAccessComponent } from './bulk-access/bulk-access.component'; @NgModule({ imports: [ @@ -47,7 +52,16 @@ import { SiteAdministratorGuard } from '../core/data/feature-authorization/featu }, data: { title: 'admin.access-control.groups.title.singleGroup', breadcrumbKey: 'admin.access-control.groups.singleGroup' }, canActivate: [GroupPageGuard] - } + }, + { + path: 'bulk-access', + component: BulkAccessComponent, + resolve: { + breadcrumb: I18nBreadcrumbResolver + }, + data: { title: 'admin.access-control.bulk-access.title', breadcrumbKey: 'admin.access-control.bulk-access' }, + canActivate: [SiteAdministratorGuard] + }, ]) ] }) diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts index 47a971a882..ba7334d24f 100644 --- a/src/app/access-control/access-control.module.ts +++ b/src/app/access-control/access-control.module.ts @@ -12,6 +12,11 @@ import { GroupsRegistryComponent } from './group-registry/groups-registry.compon import { FormModule } from '../shared/form/form.module'; import { DYNAMIC_ERROR_MESSAGES_MATCHER, DynamicErrorMessagesMatcher } from '@ng-dynamic-forms/core'; import { AbstractControl } from '@angular/forms'; +import { BulkAccessComponent } from './bulk-access/bulk-access.component'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { BulkAccessBrowseComponent } from './bulk-access/browse/bulk-access-browse.component'; +import { BulkAccessSettingsComponent } from './bulk-access/settings/bulk-access-settings.component'; +import { SearchModule } from '../shared/search/search.module'; /** * Condition for displaying error messages on email form field @@ -22,13 +27,15 @@ export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = }; @NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule, - AccessControlRoutingModule, - FormModule, - ], + imports: [ + CommonModule, + SharedModule, + RouterModule, + AccessControlRoutingModule, + FormModule, + NgbAccordionModule, + SearchModule, + ], exports: [ MembersListComponent, ], @@ -39,6 +46,9 @@ export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = GroupFormComponent, SubgroupsListComponent, MembersListComponent, + BulkAccessComponent, + BulkAccessBrowseComponent, + BulkAccessSettingsComponent, ], providers: [ { diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html new file mode 100644 index 0000000000..760134aba5 --- /dev/null +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html @@ -0,0 +1,51 @@ + + + +
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ + + diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.scss b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts new file mode 100644 index 0000000000..12cfabae08 --- /dev/null +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BulkAccessBrowseComponent } from './bulk-access-browse.component'; + +describe('BrowseComponent', () => { + let component: BulkAccessBrowseComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BulkAccessBrowseComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BulkAccessBrowseComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts new file mode 100644 index 0000000000..a91cdf35df --- /dev/null +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit } from '@angular/core'; + +import { BehaviorSubject, Subscription } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; + +import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; +import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; +import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; +import { SelectableListState } from '../../../shared/object-list/selectable-list/selectable-list.reducer'; +import { RemoteData } from '../../../core/data/remote-data'; +import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; +import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; +import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { PageInfo } from '../../../core/shared/page-info.model'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; + +@Component({ + selector: 'ds-bulk-access-browse', + templateUrl: './bulk-access-browse.component.html', + styleUrls: ['./bulk-access-browse.component.scss'], + providers: [ + { + provide: SEARCH_CONFIG_SERVICE, + useClass: SearchConfigurationService + } + ] +}) +export class BulkAccessBrowseComponent implements OnInit { + /** + * The active nav id + */ + activateId = 'search'; + + /** + * The selection list id + */ + listId: string = 'bulk-access-list'; + + /** + * The list of the objects already selected + */ + objectsSelected$: BehaviorSubject>> = new BehaviorSubject>>(null); + + paginationOptions: PaginationComponentOptions; + private subs: Subscription[] = []; + + constructor(private selectableListService: SelectableListService) { + } + + ngOnInit(): void { + this.paginationOptions = Object.assign(new PaginationComponentOptions(), { + id: 'elp', + pageSize: 10, + currentPage: 1 + }); + this.subs.push( + this.selectableListService.getSelectableList(this.listId).pipe( + distinctUntilChanged(), + map((list: SelectableListState) => { + console.log(list); + return createSuccessfulRemoteDataObject(buildPaginatedList(new PageInfo(), list?.selection || [])) + }) + ).subscribe(this.objectsSelected$) + ) + } + +} diff --git a/src/app/access-control/bulk-access/bulk-access.component.html b/src/app/access-control/bulk-access/bulk-access.component.html new file mode 100644 index 0000000000..e0eafb6e36 --- /dev/null +++ b/src/app/access-control/bulk-access/bulk-access.component.html @@ -0,0 +1,9 @@ + +
+ +
+ +
+ + + diff --git a/src/app/access-control/bulk-access/bulk-access.component.scss b/src/app/access-control/bulk-access/bulk-access.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/access-control/bulk-access/bulk-access.component.spec.ts b/src/app/access-control/bulk-access/bulk-access.component.spec.ts new file mode 100644 index 0000000000..61fab1e5a9 --- /dev/null +++ b/src/app/access-control/bulk-access/bulk-access.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BulkAccessComponent } from './bulk-access.component'; + +describe('BulkAccessComponent', () => { + let component: BulkAccessComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BulkAccessComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BulkAccessComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts new file mode 100644 index 0000000000..9e45b2c59b --- /dev/null +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ds-bulk-access', + templateUrl: './bulk-access.component.html', + styleUrls: ['./bulk-access.component.scss'] +}) +export class BulkAccessComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.html b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.html new file mode 100644 index 0000000000..a5c292eddd --- /dev/null +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.html @@ -0,0 +1,21 @@ + + + +
+ +
+
+ + +
+
+
+
+ +

bulk-access-settings works!

+
+
+
diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.scss b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts new file mode 100644 index 0000000000..435f2828b9 --- /dev/null +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BulkAccessSettingsComponent } from './bulk-access-settings.component'; + +describe('BulkAccessSettingsComponent', () => { + let component: BulkAccessSettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BulkAccessSettingsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BulkAccessSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts new file mode 100644 index 0000000000..631b786b01 --- /dev/null +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ds-bulk-access-settings', + templateUrl: './bulk-access-settings.component.html', + styleUrls: ['./bulk-access-settings.component.scss'] +}) +export class BulkAccessSettingsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index 3ec1df85fd..cad6a6ec57 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -659,6 +659,17 @@ export class MenuResolver implements Resolve { link: '/access-control/groups' } as LinkMenuItemModel, }, + { + id: 'access_control_bulk', + parentID: 'access_control', + active: false, + visible: isSiteAdmin, + model: { + type: MenuItemType.LINK, + text: 'menu.section.access_control_bulk', + link: '/access-control/bulk-access' + } as LinkMenuItemModel, + }, // TODO: enable this menu item once the feature has been implemented // { // id: 'access_control_authorizations', diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 6541848df2..00f2bd46b0 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -227,6 +227,18 @@ + "admin.access-control.bulk-access": "Bulk access", + + "admin.access-control.bulk-access.title": "Bulk Access", + + "admin.access-control.bulk-access-browse.header": "Step 1: select objects", + + "admin.access-control.bulk-access-browse.search.header": "Search", + + "admin.access-control.bulk-access-browse.selected.header": "Current selection({{number}})", + + "admin.access-control.bulk-access-settings.header": "Step 2: operation to perform", + "admin.access-control.epeople.actions.delete": "Delete EPerson", "admin.access-control.epeople.actions.impersonate": "Impersonate EPerson", @@ -2878,6 +2890,8 @@ "menu.section.access_control_authorizations": "Authorizations", + "menu.section.access_control_bulk": "Bulk access", + "menu.section.access_control_groups": "Groups", "menu.section.access_control_people": "People", From 581ed432f9843c8f5a417a93e80494e2125bcffd Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 10 May 2023 11:09:41 +0200 Subject: [PATCH 08/45] [CST-9636] Use show thumbnail flag for community and collection list elements --- .../collection-search-result-list-element.component.ts | 2 +- .../community-search-result-list-element.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts index 60415f649e..23e9e5ee57 100644 --- a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts @@ -23,7 +23,7 @@ export class CollectionSearchResultListElementComponent extends SearchResultList ngOnInit(): void { super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; + this.showThumbnails = this.showThumbnails ?? this.appConfig.browseBy.showThumbnails; } } diff --git a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts index 4cc25b8b76..f31e36ce82 100644 --- a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts @@ -23,6 +23,6 @@ export class CommunitySearchResultListElementComponent extends SearchResultListE ngOnInit(): void { super.ngOnInit(); - this.showThumbnails = this.appConfig.browseBy.showThumbnails; + this.showThumbnails = this.showThumbnails ?? this.appConfig.browseBy.showThumbnails; } } From 3924a820482944055d8ac6d3f7efab95f433050e Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 10 May 2023 13:08:08 +0200 Subject: [PATCH 09/45] [CST-9636] Provide possibility to paginate a full list of elements --- .../object-list/object-list.component.html | 59 +++++++++++++------ .../object-list/object-list.component.ts | 5 ++ .../themed-object-list.component.ts | 6 ++ .../pagination/pagination.component.html | 4 +- src/app/shared/shared.module.ts | 4 +- 5 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 8e0482b1f7..541a4794b0 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -16,23 +16,46 @@ (prev)="goPrev()" (next)="goNext()">
    -
  • - - - -
  • + +
  • + + + +
  • +
    + +
  • + + + +
  • +
diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index 5161b75459..72bff54f59 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -76,6 +76,11 @@ export class ObjectListComponent { */ @Input() importConfig: { buttonLabel: string }; + /** + * If true the object list provided needs to be paginated using the `paginate` pipe + */ + @Input() listToPaginate = false; + /** * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination */ diff --git a/src/app/shared/object-list/themed-object-list.component.ts b/src/app/shared/object-list/themed-object-list.component.ts index 4a04061a5c..9448ed5939 100644 --- a/src/app/shared/object-list/themed-object-list.component.ts +++ b/src/app/shared/object-list/themed-object-list.component.ts @@ -44,6 +44,11 @@ export class ThemedObjectListComponent extends ThemedComponent - -
-
- - diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts index 35ba6076f9..4192fe5a9a 100644 --- a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.ts @@ -1,64 +1,24 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { shareReplay } from 'rxjs'; -import { - AccessControlArrayFormComponent -} from '../../../shared/access-control-array-form/access-control-array-form.component'; -import { CollectionAccessControlService } from './collection-access-control.service'; +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Community } from '../../../core/shared/community.model'; +import { ActivatedRoute } from '@angular/router'; +import { map } from 'rxjs/operators'; +import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; @Component({ selector: 'ds-collection-access-control', templateUrl: './collection-access-control.component.html', styleUrls: ['./collection-access-control.component.scss'], - providers: [CollectionAccessControlService] }) export class CollectionAccessControlComponent implements OnInit { + itemRD$: Observable>; - @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; - @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; - - constructor(private collectionAccessControlService: CollectionAccessControlService) {} - - state = initialState; - - dropdownData$ = this.collectionAccessControlService.dropdownData$.pipe( - shareReplay(1) - ); + constructor(private route: ActivatedRoute) {} ngOnInit(): void { - + this.itemRD$ = this.route.parent.parent.data.pipe( + map((data) => data.dso) + ).pipe(getFirstSucceededRemoteData()) as Observable>; } - - reset() { - this.bitstreamAccessCmp.reset(); - this.itemAccessCmp.reset(); - this.state = initialState; - } - - submit() { - const bitstreamAccess = this.bitstreamAccessCmp.getValue(); - const itemAccess = this.itemAccessCmp.getValue(); - - console.log('bitstreamAccess', bitstreamAccess); - console.log('itemAccess', itemAccess); - } - - handleStatusChange(type: 'item' | 'bitstream', active: boolean) { - if (type === 'bitstream') { - active ? this.bitstreamAccessCmp.enable() : this.bitstreamAccessCmp.disable(); - } else if (type === 'item') { - active ? this.itemAccessCmp.enable() : this.itemAccessCmp.disable(); - } - } - } - -const initialState = { - item: { - toggleStatus: false, - accessMode: '', - }, - bitstream: { - toggleStatus: false, - accessMode: '', - }, -}; diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts deleted file mode 100644 index 3267461fe6..0000000000 --- a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.service.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Injectable } from '@angular/core'; -import { AccessControlItem } from 'src/app/core/shared/bulk-access-condition-options.model'; -import { Observable, of } from 'rxjs'; - -export interface AccessControlDropdownDataResponse { - id: string; - itemAccessConditionOptions: AccessControlItem[]; - bitstreamAccessConditionOptions: AccessControlItem[]; -} - -@Injectable() -export class CollectionAccessControlService { - dropdownData$: Observable = of(accessControlDropdownData); -} - -const accessControlDropdownData: AccessControlDropdownDataResponse = { - 'id': 'default', - 'itemAccessConditionOptions': [ - { - 'name': 'openaccess' - }, - { - 'name': 'administrator' - }, - { - 'name': 'embargo', - 'hasStartDate': true, - 'maxStartDate': '2018-06-24T00:40:54.970+0000' - }, - { - 'name': 'lease', - 'hasEndDate': true, - 'maxEndDate': '2017-12-24T00:40:54.970+0000' - } - ], - 'bitstreamAccessConditionOptions': [ - { - 'name': 'openaccess' - }, - { - 'name': 'administrator' - }, - { - 'name': 'embargo', - 'hasStartDate': true, - 'maxStartDate': '2018-06-24T00:40:54.970+0000' - }, - { - 'name': 'lease', - 'hasEndDate': true, - 'maxEndDate': '2017-12-24T00:40:54.970+0000' - } - ] -}; diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts index 5af7d3189a..9d703c0cf4 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts @@ -18,6 +18,9 @@ import { AccessControlArrayFormModule } from '../../shared/access-control-array-form/access-control-array-form.component'; import { UiSwitchModule } from 'ngx-ui-switch'; +import { + AccessControlFormContainerModule +} from '../../shared/access-control-form-container/access-control-form-container.component'; /** * Module that contains all components related to the Edit Collection page administrator functionality @@ -33,6 +36,7 @@ import { UiSwitchModule } from 'ngx-ui-switch'; ComcolModule, AccessControlArrayFormModule, UiSwitchModule, + AccessControlFormContainerModule, ], declarations: [ EditCollectionPageComponent, diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html index 26f394620a..89999955eb 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html @@ -1,120 +1,6 @@ -
-
-
-

{{ 'community-access-control-title' | translate }}

- -
-
-
-

- {{ 'access-control-item-header-toggle' | translate }} -

- - -
- -
-
- {{'access-control-mode' | translate}} -
-
-
- - -
-
- - -
-
-
- -
-
{{'access-control-access-conditions' | translate}}
- -
- {{'access-control-no-access-conditions-warning-message' | translate}} -
-
- - - - -
-
-
-

- {{ 'access-control-bitstream-header-toggle' | translate }} -

- -
- -
-
- {{'access-control-mode' | translate}} -
-
-
- - -
-
- - -
-
-
- -
-
{{'access-control-access-conditions' | translate}}
- -
- {{'access-control-no-access-conditions-warning-message' | translate}} -
-
- - - - -
-
- -
- -
- - -
-
-
-
+ +

{{'community-access-control-title' | translate }}

+
diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts index a25adb177c..b24069b9ac 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts @@ -1,63 +1,26 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { CommunityAccessControlService } from './community-access-control.service'; -import { shareReplay } from 'rxjs'; -import { - AccessControlArrayFormComponent -} from '../../../shared/access-control-array-form/access-control-array-form.component'; +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute } from '@angular/router'; +import { map } from 'rxjs/operators'; +import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; +import { Community } from '../../../core/shared/community.model'; @Component({ selector: 'ds-community-access-control', templateUrl: './community-access-control.component.html', styleUrls: ['./community-access-control.component.scss'], - providers: [CommunityAccessControlService] }) export class CommunityAccessControlComponent implements OnInit { + itemRD$: Observable>; - @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; - @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; - - constructor(private communityAccessControlService: CommunityAccessControlService) {} - - state = initialState; - - dropdownData$ = this.communityAccessControlService.dropdownData$.pipe( - shareReplay(1) - ); + constructor(private route: ActivatedRoute) {} ngOnInit(): void { - - } - - reset() { - this.bitstreamAccessCmp.reset(); - this.itemAccessCmp.reset(); - this.state = initialState; - } - - submit() { - const bitstreamAccess = this.bitstreamAccessCmp.getValue(); - const itemAccess = this.itemAccessCmp.getValue(); - - console.log('bitstreamAccess', bitstreamAccess); - console.log('itemAccess', itemAccess); - } - - handleStatusChange(type: 'item' | 'bitstream', active: boolean) { - if (type === 'bitstream') { - active ? this.bitstreamAccessCmp.enable() : this.bitstreamAccessCmp.disable(); - } else if (type === 'item') { - active ? this.itemAccessCmp.enable() : this.itemAccessCmp.disable(); - } + this.itemRD$ = this.route.parent.parent.data.pipe( + map((data) => data.dso) + ).pipe(getFirstSucceededRemoteData()) as Observable>; } } - -const initialState = { - item: { - toggleStatus: false, - accessMode: '', - }, - bitstream: { - toggleStatus: false, - accessMode: '', - }, -}; diff --git a/src/app/community-page/edit-community-page/edit-community-page.module.ts b/src/app/community-page/edit-community-page/edit-community-page.module.ts index 8aa52086ee..5ed2e401c6 100644 --- a/src/app/community-page/edit-community-page/edit-community-page.module.ts +++ b/src/app/community-page/edit-community-page/edit-community-page.module.ts @@ -15,6 +15,9 @@ import { UiSwitchModule } from 'ngx-ui-switch'; import { AccessControlArrayFormModule } from '../../shared/access-control-array-form/access-control-array-form.component'; +import { + AccessControlFormContainerModule +} from '../../shared/access-control-form-container/access-control-form-container.component'; /** * Module that contains all components related to the Edit Community page administrator functionality @@ -29,6 +32,7 @@ import { ResourcePoliciesModule, UiSwitchModule, AccessControlArrayFormModule, + AccessControlFormContainerModule, ], declarations: [ EditCommunityPageComponent, diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index fb46697c7d..322c6c6999 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { NgbTooltipModule, NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { SharedModule } from '../../shared/shared.module'; import { EditItemPageRoutingModule } from './edit-item-page.routing.module'; @@ -20,14 +20,22 @@ import { SearchPageModule } from '../../search-page/search-page.module'; import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component'; import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component'; import { EditRelationshipComponent } from './item-relationships/edit-relationship/edit-relationship.component'; -import { EditRelationshipListComponent } from './item-relationships/edit-relationship-list/edit-relationship-list.component'; +import { + EditRelationshipListComponent +} from './item-relationships/edit-relationship-list/edit-relationship-list.component'; import { AbstractItemUpdateComponent } from './abstract-item-update/abstract-item-update.component'; import { ItemMoveComponent } from './item-move/item-move.component'; -import { ItemEditBitstreamBundleComponent } from './item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component'; +import { + ItemEditBitstreamBundleComponent +} from './item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component'; import { BundleDataService } from '../../core/data/bundle-data.service'; import { DragDropModule } from '@angular/cdk/drag-drop'; -import { ItemEditBitstreamDragHandleComponent } from './item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component'; -import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component'; +import { + ItemEditBitstreamDragHandleComponent +} from './item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component'; +import { + PaginatedDragAndDropBitstreamListComponent +} from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component'; import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component'; import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component'; import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component'; @@ -43,10 +51,10 @@ import { AccessControlArrayFormModule } from '../../shared/access-control-array-form/access-control-array-form.component'; import { UiSwitchModule } from 'ngx-ui-switch'; -import { - ItemAccessControlSelectBitstreamsModalComponent -} from './item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; import { ResultsBackButtonModule } from '../../shared/results-back-button/results-back-button.module'; +import { + AccessControlFormContainerModule +} from '../../shared/access-control-form-container/access-control-form-container.component'; /** @@ -67,6 +75,7 @@ import { ResultsBackButtonModule } from '../../shared/results-back-button/result AccessControlArrayFormModule, UiSwitchModule, ResultsBackButtonModule, + AccessControlFormContainerModule, ], declarations: [ EditItemPageComponent, @@ -95,7 +104,6 @@ import { ResultsBackButtonModule } from '../../shared/results-back-button/result IdentifierDataComponent, ItemRegisterDoiComponent, ItemAccessControlComponent, - ItemAccessControlSelectBitstreamsModalComponent ], providers: [ BundleDataService, diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html index 6b65f2fe8b..7bef310717 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html @@ -1,159 +1,6 @@ -
-
-
-

{{ 'item-access-control-title' | translate }}

- -
-
-
-

- {{ 'access-control-item-header-toggle' | translate }} -

- - -
- -
-
- {{ 'access-control-mode' | translate }} -
-
-
- - -
-
- - -
-
-
- -
-
{{'access-control-access-conditions' | translate}}
-
- {{'access-control-no-access-conditions-warning-message' | translate}} -
-
- - - - -
-
-
-

- {{'access-control-bitstream-header-toggle' | translate}} -

- - -
- -
-
- {{'access-control-limit-to-specific' | translate}} -
-
-
- - -
-
- - -
-
-
- -
-
- {{'access-control-mode' | translate}} -
-
-
- - -
-
- - -
-
-
- -
-
- {{'access-control-access-conditions' | translate}} -
- -
- {{'access-control-no-access-conditions-warning-message' | translate}} -
-
- - - - -
-
- -
- -
- - -
-
-
-
+ +

{{ 'item-access-control-title' | translate }}

+
diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts index 29f1d55ada..096c66577a 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts @@ -1,48 +1,24 @@ -import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { - AccessControlArrayFormComponent -} from '../../../shared/access-control-array-form/access-control-array-form.component'; -import { concatMap, Observable, shareReplay } from 'rxjs'; +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; import { ItemAccessControlService } from './item-access-control.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { - ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID, - ItemAccessControlSelectBitstreamsModalComponent -} from './item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; -import { map, take } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; -import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; @Component({ selector: 'ds-item-access-control', templateUrl: './item-access-control.component.html', styleUrls: [ './item-access-control.component.scss' ], - providers: [ ItemAccessControlService ] }) -export class ItemAccessControlComponent implements OnInit, OnDestroy { +export class ItemAccessControlComponent implements OnInit { itemRD$: Observable>; - @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; - @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; - - constructor( - private itemAccessControlService: ItemAccessControlService, - private selectableListService: SelectableListService, - protected modalService: NgbModal, - private route: ActivatedRoute, - private cdr: ChangeDetectorRef - ) {} - - state = initialState; - - dropdownData$ = this.itemAccessControlService.dropdownData$.pipe( - shareReplay(1) - ); + constructor(private route: ActivatedRoute) {} ngOnInit(): void { this.itemRD$ = this.route.parent.parent.data.pipe( @@ -50,59 +26,4 @@ export class ItemAccessControlComponent implements OnInit, OnDestroy { ).pipe(getFirstSucceededRemoteData()) as Observable>; } - reset() { - this.bitstreamAccessCmp.reset(); - this.itemAccessCmp.reset(); - this.state = initialState; - } - - submit() { - const bitstreamAccess = this.bitstreamAccessCmp.getValue(); - const itemAccess = this.itemAccessCmp.getValue(); - - this.itemAccessControlService.execute({ - bitstreamAccess, - itemAccess, - state: this.state - }); - } - - handleStatusChange(type: 'item' | 'bitstream', active: boolean) { - if (type === 'bitstream') { - active ? this.bitstreamAccessCmp.enable() : this.bitstreamAccessCmp.disable(); - } else if (type === 'item') { - active ? this.itemAccessCmp.enable() : this.itemAccessCmp.disable(); - } - } - - openSelectBitstreamsModal(item: Item) { - const ref = this.modalService.open(ItemAccessControlSelectBitstreamsModalComponent); - ref.componentInstance.selectedBitstreams = this.state.bitstream.selectedBitstreams; - ref.componentInstance.item = item; - - ref.closed.pipe( - concatMap(() => this.selectableListService.getSelectableList(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID)), - take(1) - ).subscribe((list) => { - this.state.bitstream.selectedBitstreams = list.selection; - this.cdr.detectChanges(); - }); - } - - ngOnDestroy(): void { - this.selectableListService.deselectAll(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID); - } } - -const initialState = { - item: { - toggleStatus: false, - accessMode: '', - }, - bitstream: { - toggleStatus: false, - accessMode: '', - changesLimit: '', // 'all' | 'selected' - selectedBitstreams: [] as ListableObject[], - }, -}; diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts index 57b28f81b0..6bdc523aa8 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts @@ -8,7 +8,7 @@ export interface AccessControlDropdownDataResponse { bitstreamAccessConditionOptions: AccessControlItem[]; } -@Injectable() +@Injectable({ providedIn: 'root' }) export class ItemAccessControlService { dropdownData$: Observable = of(accessControlDropdownData); diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.html b/src/app/shared/access-control-form-container/access-control-form-container.component.html new file mode 100644 index 0000000000..bcf385f0d4 --- /dev/null +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.html @@ -0,0 +1,163 @@ +
+
+
+ + + + + + +
+
+
+

+ {{ 'access-control-item-header-toggle' | translate }} +

+ + +
+ +
+
+ {{ 'access-control-mode' | translate }} +
+
+
+ + +
+
+ + +
+
+
+ +
+
{{'access-control-access-conditions' | translate}}
+
+ {{'access-control-no-access-conditions-warning-message' | translate}} +
+
+ + + + +
+
+
+

+ {{'access-control-bitstream-header-toggle' | translate}} +

+ + +
+ +
+
+ {{'access-control-limit-to-specific' | translate}} +
+
+
+ + +
+
+ + +
+
+
+ +
+
+ {{'access-control-mode' | translate}} +
+
+
+ + +
+
+ + +
+
+
+ +
+
+ {{'access-control-access-conditions' | translate}} +
+ +
+ {{'access-control-no-access-conditions-warning-message' | translate}} +
+
+ + + + +
+
+ +
+ +
+ + +
+
+
+
diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.scss b/src/app/shared/access-control-form-container/access-control-form-container.component.scss similarity index 100% rename from src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.scss rename to src/app/shared/access-control-form-container/access-control-form-container.component.scss diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts new file mode 100644 index 0000000000..871e301209 --- /dev/null +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AccessControlFormContainerComponent } from './access-control-form-container.component'; + +describe('AccessControlFormContainerComponent', () => { + let component: AccessControlFormContainerComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AccessControlFormContainerComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AccessControlFormContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts new file mode 100644 index 0000000000..00030f8474 --- /dev/null +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -0,0 +1,119 @@ +import { ChangeDetectorRef, Component, Input, NgModule, ViewChild } from '@angular/core'; +import { concatMap, shareReplay } from 'rxjs'; +import { RemoteData } from '../../core/data/remote-data'; +import { Item } from '../../core/shared/item.model'; +import { + AccessControlArrayFormComponent, + AccessControlArrayFormModule +} from '../access-control-array-form/access-control-array-form.component'; +import { + ItemAccessControlService +} from '../../item-page/edit-item-page/item-access-control/item-access-control.service'; +import { SelectableListService } from '../object-list/selectable-list/selectable-list.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { take } from 'rxjs/operators'; +import { CommonModule } from '@angular/common'; +import { ListableObject } from '../object-collection/shared/listable-object.model'; +import { SharedModule } from '../shared.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { UiSwitchModule } from 'ngx-ui-switch'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { + ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID, + ItemAccessControlSelectBitstreamsModalComponent +} from './item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; + +@Component({ + selector: 'ds-access-control-form-container', + templateUrl: './access-control-form-container.component.html', + styleUrls: ['./access-control-form-container.component.scss'] +}) +export class AccessControlFormContainerComponent { + + @Input() showLimitToSpecificBitstreams = false; + @Input() itemRD: RemoteData; + + @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; + @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; + + constructor( + private itemAccessControlService: ItemAccessControlService, + private selectableListService: SelectableListService, + protected modalService: NgbModal, + private cdr: ChangeDetectorRef + ) {} + + state = initialState; + + dropdownData$ = this.itemAccessControlService.dropdownData$.pipe( + shareReplay(1) + ); + + reset() { + this.bitstreamAccessCmp.reset(); + this.itemAccessCmp.reset(); + this.state = initialState; + } + + submit() { + const bitstreamAccess = this.bitstreamAccessCmp.getValue(); + const itemAccess = this.itemAccessCmp.getValue(); + + this.itemAccessControlService.execute({ + bitstreamAccess, + itemAccess, + state: this.state + }); + } + + handleStatusChange(type: 'item' | 'bitstream', active: boolean) { + if (type === 'bitstream') { + active ? this.bitstreamAccessCmp.enable() : this.bitstreamAccessCmp.disable(); + } else if (type === 'item') { + active ? this.itemAccessCmp.enable() : this.itemAccessCmp.disable(); + } + } + + openSelectBitstreamsModal(item: Item) { + const ref = this.modalService.open(ItemAccessControlSelectBitstreamsModalComponent); + ref.componentInstance.selectedBitstreams = this.state.bitstream.selectedBitstreams; + ref.componentInstance.item = item; + + ref.closed.pipe( + concatMap(() => this.selectableListService.getSelectableList(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID)), + take(1) + ).subscribe((list) => { + this.state.bitstream.selectedBitstreams = list.selection; + this.cdr.detectChanges(); + }); + } + + // eslint-disable-next-line @angular-eslint/use-lifecycle-interface + ngOnDestroy(): void { + this.selectableListService.deselectAll(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID); + } +} + + +const initialState = { + item: { + toggleStatus: false, + accessMode: '', + }, + bitstream: { + toggleStatus: false, + accessMode: '', + changesLimit: '', // 'all' | 'selected' + selectedBitstreams: [] as ListableObject[], + }, +}; + + +@NgModule({ + imports: [ CommonModule, AccessControlArrayFormModule, SharedModule, TranslateModule, UiSwitchModule ], + exports: [AccessControlFormContainerComponent], + declarations: [ AccessControlFormContainerComponent, ItemAccessControlSelectBitstreamsModalComponent ], +}) +export class AccessControlFormContainerModule {} + + diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html similarity index 100% rename from src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html rename to src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html diff --git a/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.scss b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.spec.ts b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.spec.ts similarity index 100% rename from src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.spec.ts rename to src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.spec.ts diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts similarity index 69% rename from src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts rename to src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts index dbae803c44..171b1d59c1 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts +++ b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts @@ -1,17 +1,17 @@ import { Component, Input, OnInit } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { TranslateService } from '@ngx-translate/core'; -import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { BehaviorSubject } from 'rxjs'; -import { Item } from '../../../../core/shared/item.model'; -import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; -import { PaginatedList } from '../../../../core/data/paginated-list.model'; -import { Bitstream } from '../../../../core/shared/bitstream.model'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; -import { hasValue } from '../../../../shared/empty.util'; -import { Context } from '../../../../core/shared/context.model'; +import { PaginatedList } from 'src/app/core/data/paginated-list.model'; +import { RemoteData } from 'src/app/core/data/remote-data'; +import { Bitstream } from 'src/app/core/shared/bitstream.model'; +import { Context } from 'src/app/core/shared/context.model'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { Item } from '../../../core/shared/item.model'; +import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { TranslateService } from '@ngx-translate/core'; +import { hasValue } from '../../empty.util'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; export const ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID = 'item-access-control-select-bitstreams' From 723e1e1278a3c5a12f02d5b0375cc53da3999614 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 10 May 2023 15:11:44 +0200 Subject: [PATCH 11/45] [CST-9636] Make access control form container reusable --- .../browse/bulk-access-browse.component.ts | 54 +++++++++++++++++-- .../item-access-control.component.ts | 3 -- ...cess-control-form-container.component.html | 11 ++-- ...access-control-form-container.component.ts | 45 ++++++++++++---- .../bulk-access-control.service.ts} | 25 +++++++-- 5 files changed, 110 insertions(+), 28 deletions(-) rename src/app/{item-page/edit-item-page/item-access-control/item-access-control.service.ts => shared/access-control-form-container/bulk-access-control.service.ts} (66%) diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts index a91cdf35df..e6dba0cafe 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -16,7 +16,56 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio @Component({ selector: 'ds-bulk-access-browse', - templateUrl: './bulk-access-browse.component.html', + template: ` + + + +
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ `, styleUrls: ['./bulk-access-browse.component.scss'], providers: [ { @@ -44,8 +93,7 @@ export class BulkAccessBrowseComponent implements OnInit { paginationOptions: PaginationComponentOptions; private subs: Subscription[] = []; - constructor(private selectableListService: SelectableListService) { - } + constructor(private selectableListService: SelectableListService) {} ngOnInit(): void { this.paginationOptions = Object.assign(new PaginationComponentOptions(), { diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts index 096c66577a..874b624494 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.ts @@ -1,13 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; -import { ItemAccessControlService } from './item-access-control.service'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { map } from 'rxjs/operators'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; -import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; @Component({ selector: 'ds-item-access-control', diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.html b/src/app/shared/access-control-form-container/access-control-form-container.component.html index bcf385f0d4..71175ce588 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.html +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.html @@ -3,10 +3,6 @@
- - - -
@@ -95,7 +91,8 @@ @@ -148,9 +145,9 @@
-
+
-
+
diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 00030f8474..a2e9cb08ab 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -6,9 +6,7 @@ import { AccessControlArrayFormComponent, AccessControlArrayFormModule } from '../access-control-array-form/access-control-array-form.component'; -import { - ItemAccessControlService -} from '../../item-page/edit-item-page/item-access-control/item-access-control.service'; +import { BulkAccessControlService } from './bulk-access-control.service'; import { SelectableListService } from '../object-list/selectable-list/selectable-list.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { take } from 'rxjs/operators'; @@ -26,18 +24,21 @@ import { @Component({ selector: 'ds-access-control-form-container', templateUrl: './access-control-form-container.component.html', - styleUrls: ['./access-control-form-container.component.scss'] + styleUrls: [ './access-control-form-container.component.scss' ], + exportAs: 'dsAccessControlForm' }) export class AccessControlFormContainerComponent { @Input() showLimitToSpecificBitstreams = false; @Input() itemRD: RemoteData; + @Input() hideSubmit = false; + @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; constructor( - private itemAccessControlService: ItemAccessControlService, + private bulkAccessControlService: BulkAccessControlService, private selectableListService: SelectableListService, protected modalService: NgbModal, private cdr: ChangeDetectorRef @@ -45,10 +46,18 @@ export class AccessControlFormContainerComponent { state = initialState; - dropdownData$ = this.itemAccessControlService.dropdownData$.pipe( + dropdownData$ = this.bulkAccessControlService.dropdownData$.pipe( shareReplay(1) ); + getFormValue() { + return { + bitstream: this.bitstreamAccessCmp.getValue(), + item: this.itemAccessCmp.getValue(), + state: this.state + }; + } + reset() { this.bitstreamAccessCmp.reset(); this.itemAccessCmp.reset(); @@ -59,11 +68,18 @@ export class AccessControlFormContainerComponent { const bitstreamAccess = this.bitstreamAccessCmp.getValue(); const itemAccess = this.itemAccessCmp.getValue(); - this.itemAccessControlService.execute({ + const { file } = this.bulkAccessControlService.createPayloadFile({ bitstreamAccess, itemAccess, state: this.state }); + + this.bulkAccessControlService.executeScript( + [ this.itemRD.payload.uuid ], + file + ).pipe(take(1)).subscribe((res) => { + console.log('success', res); + }); } handleStatusChange(type: 'item' | 'bitstream', active: boolean) { @@ -110,9 +126,18 @@ const initialState = { @NgModule({ - imports: [ CommonModule, AccessControlArrayFormModule, SharedModule, TranslateModule, UiSwitchModule ], - exports: [AccessControlFormContainerComponent], - declarations: [ AccessControlFormContainerComponent, ItemAccessControlSelectBitstreamsModalComponent ], + imports: [ + CommonModule, + AccessControlArrayFormModule, + SharedModule, + TranslateModule, + UiSwitchModule + ], + declarations: [ + AccessControlFormContainerComponent, + ItemAccessControlSelectBitstreamsModalComponent + ], + exports: [ AccessControlFormContainerComponent ], }) export class AccessControlFormContainerModule {} diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts similarity index 66% rename from src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts rename to src/app/shared/access-control-form-container/bulk-access-control.service.ts index 6bdc523aa8..85ec05cd60 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -1,6 +1,8 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; -import { AccessControlItem } from '../../../core/shared/bulk-access-condition-options.model'; +import { AccessControlItem } from '../../core/shared/bulk-access-condition-options.model'; +import { ScriptDataService } from '../../core/data/processes/script-data.service'; +import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; export interface AccessControlDropdownDataResponse { id: string; @@ -9,11 +11,12 @@ export interface AccessControlDropdownDataResponse { } @Injectable({ providedIn: 'root' }) -export class ItemAccessControlService { +export class BulkAccessControlService { + constructor(private scriptService: ScriptDataService) {} + dropdownData$: Observable = of(accessControlDropdownData); - - execute(payload: any) { + createPayloadFile(payload: any) { console.log('execute', payload); const blob = new Blob([JSON.stringify(payload, null, 2)], { @@ -25,7 +28,19 @@ export class ItemAccessControlService { }); const url = URL.createObjectURL(file); - window.open(url, '_blank'); + window.open(url, '_blank'); // remove this later + + return { url, file }; + } + + executeScript(uuids: string[], file: File) { + console.log('execute', { uuids, file }); + + const params: ProcessParameter[] = [ + { name: 'uuid', value: uuids.join(',') }, + ]; + + return this.scriptService.invoke('bulk-access-control', params, [file]); } } From 35d4561d53f75c40c307c01a084d50fb780020b0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 10 May 2023 15:12:55 +0200 Subject: [PATCH 12/45] [CST-9636] WIP improve bulk access page --- .../browse/bulk-access-browse.component.html | 23 +++-- .../browse/bulk-access-browse.component.ts | 89 +++++++++++++++---- .../bulk-access/bulk-access.component.html | 4 +- .../bulk-access/bulk-access.component.ts | 6 ++ .../bulk-access-settings.component.ts | 7 +- 5 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html index 760134aba5..9851aab835 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html @@ -22,7 +22,7 @@ {{'admin.access-control.bulk-access-browse.search.header' | translate}}
- @@ -30,15 +30,20 @@
  • - {{'admin.access-control.bulk-access-browse.selected.header' | translate: {number: ((objectsSelected$ | async)?.payload?.totalElements) ? (objectsSelected$ | async)?.payload?.totalElements : '0'} }} + + {{'admin.access-control.bulk-access-browse.selected.header' | translate: {number: ((objectsSelected$ | async)?.payload?.totalElements) ? (objectsSelected$ | async)?.payload?.totalElements : '0'} }} + - +
  • diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts index a91cdf35df..ed94398f6d 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { BehaviorSubject, Subscription } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; @@ -13,6 +13,7 @@ import { ListableObject } from '../../../shared/object-collection/shared/listabl import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; import { PageInfo } from '../../../core/shared/page-info.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { hasValue } from '../../../shared/empty.util'; @Component({ selector: 'ds-bulk-access-browse', @@ -25,43 +26,97 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio } ] }) -export class BulkAccessBrowseComponent implements OnInit { +export class BulkAccessBrowseComponent implements OnInit, OnDestroy { + + /** + * The selection list id + */ + @Input() listId!: string; + /** * The active nav id */ activateId = 'search'; - /** - * The selection list id - */ - listId: string = 'bulk-access-list'; - /** * The list of the objects already selected */ objectsSelected$: BehaviorSubject>> = new BehaviorSubject>>(null); - paginationOptions: PaginationComponentOptions; + /** + * The pagination options object used for the list of selected elements + */ + paginationOptions$: BehaviorSubject = new BehaviorSubject(Object.assign(new PaginationComponentOptions(), { + id: 'bas', + pageSize: 5, + currentPage: 1 + })); + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + */ private subs: Subscription[] = []; constructor(private selectableListService: SelectableListService) { } + /** + * Subscribe to selectable list updates + */ ngOnInit(): void { - this.paginationOptions = Object.assign(new PaginationComponentOptions(), { - id: 'elp', - pageSize: 10, - currentPage: 1 - }); + this.subs.push( this.selectableListService.getSelectableList(this.listId).pipe( distinctUntilChanged(), - map((list: SelectableListState) => { - console.log(list); - return createSuccessfulRemoteDataObject(buildPaginatedList(new PageInfo(), list?.selection || [])) - }) + map((list: SelectableListState) => this.generatePaginatedListBySelectedElements(list)) ).subscribe(this.objectsSelected$) ) } + pageNext() { + this.paginationOptions$.next(Object.assign(new PaginationComponentOptions(), this.paginationOptions$.value, { + currentPage: this.paginationOptions$.value.currentPage + 1 + })); + console.log(this.paginationOptions$.value); + } + + pagePrev() { + this.paginationOptions$.next(Object.assign(new PaginationComponentOptions(), this.paginationOptions$.value, { + currentPage: this.paginationOptions$.value.currentPage - 1 + })); + console.log(this.paginationOptions$.value); + } + + private calculatePageCount(pageSize, totalCount = 0) { + // we suppose that if we have 0 items we want 1 empty page + return totalCount < pageSize ? 1 : Math.ceil(totalCount / pageSize); + }; + + /** + * Generate The RemoteData object containing the list of the selected elements + * @param list + * @private + */ + private generatePaginatedListBySelectedElements(list: SelectableListState): RemoteData> { + const pageInfo = new PageInfo({ + elementsPerPage: this.paginationOptions$.value.pageSize, + totalElements: list?.selection.length, + totalPages: this.calculatePageCount(this.paginationOptions$.value.pageSize, list?.selection.length), + currentPage: this.paginationOptions$.value.currentPage + }); + if (pageInfo.currentPage > pageInfo.totalPages) { + pageInfo.currentPage = pageInfo.totalPages; + this.paginationOptions$.next(Object.assign(new PaginationComponentOptions(), this.paginationOptions$.value, { + currentPage: pageInfo.currentPage + })); + } + return createSuccessfulRemoteDataObject(buildPaginatedList(pageInfo, list?.selection || [])); + } + + ngOnDestroy(): void { + this.subs + .filter((sub) => hasValue(sub)) + .forEach((sub) => sub.unsubscribe()); + this.selectableListService.deselectAll(this.listId) + } } diff --git a/src/app/access-control/bulk-access/bulk-access.component.html b/src/app/access-control/bulk-access/bulk-access.component.html index e0eafb6e36..12ab88cd1a 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.html +++ b/src/app/access-control/bulk-access/bulk-access.component.html @@ -1,8 +1,8 @@
    - +
    - +
    diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts index 9e45b2c59b..bc0d293e61 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -7,6 +7,12 @@ import { Component, OnInit } from '@angular/core'; }) export class BulkAccessComponent implements OnInit { + + /** + * The selection list id + */ + listId: string = 'bulk-access-list'; + constructor() { } ngOnInit(): void { diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts index 631b786b01..cad9e5b981 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'ds-bulk-access-settings', @@ -7,6 +7,11 @@ import { Component, OnInit } from '@angular/core'; }) export class BulkAccessSettingsComponent implements OnInit { + /** + * The selection list id + */ + @Input() listId!: string; + constructor() { } ngOnInit(): void { From 5455c79563d75b69e80240ad981f4e7c684d541d Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 10 May 2023 15:53:00 +0200 Subject: [PATCH 13/45] [CST-9636] WIP add bulk-access-config-data.service --- .../browse/bulk-access-browse.component.ts | 51 +--------------- .../community-access-control.service.ts | 59 ------------------- .../bulk-access-config-data.service.ts} | 18 +++--- src/app/core/config/config-data.service.ts | 8 ++- .../bulk-access-condition-options.model.ts | 38 ++++++++++++ .../bulk-access-condition-options.model.ts | 45 -------------- .../access-control-array-form.component.html | 6 +- .../access-control-array-form.component.ts | 29 ++++++--- .../control-max-end-date.pipe.ts | 4 +- .../control-max-start-date.pipe.ts | 5 +- ...cess-control-form-container.component.html | 10 +--- ...access-control-form-container.component.ts | 17 ++++-- .../bulk-access-control.service.ts | 51 +--------------- 13 files changed, 97 insertions(+), 244 deletions(-) delete mode 100644 src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts rename src/app/core/{data/bulk-access-condition-options.service.ts => config/bulk-access-config-data.service.ts} (53%) create mode 100644 src/app/core/config/models/bulk-access-condition-options.model.ts delete mode 100644 src/app/core/shared/bulk-access-condition-options.model.ts diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts index 43adae7ec0..7cbb8740a7 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -17,56 +17,7 @@ import { hasValue } from '../../../shared/empty.util'; @Component({ selector: 'ds-bulk-access-browse', - template: ` - - - -
    - -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - `, + templateUrl: 'bulk-access-browse.component.html', styleUrls: ['./bulk-access-browse.component.scss'], providers: [ { diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts deleted file mode 100644 index 89534683b7..0000000000 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.service.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Injectable } from '@angular/core'; -import { of } from 'rxjs'; -import { BulkAccessConditionOptionsService } from '../../../core/data/bulk-access-condition-options.service'; -import { BulkAccessConditionOptions } from '../../../core/shared/bulk-access-condition-options.model'; - - -@Injectable() -export class CommunityAccessControlService { - constructor(private service: BulkAccessConditionOptionsService) {} - - dropdownData$ = of(accessControlDropdownData); - - // dropdownData$ = this.service.getAll().pipe( - // getAllSucceededRemoteData(), - // filter((data) => data.hasSucceeded), - // map((data) => data.payload) - // ); -} - -const accessControlDropdownData: BulkAccessConditionOptions = { - _links: { self: undefined }, type: undefined, uuid: '', - 'id': 'default', - 'itemAccessConditionOptions': [ - { - 'name': 'openaccess' - }, - { - 'name': 'administrator' - }, - { - 'name': 'embargo', - 'hasStartDate': true, - 'maxStartDate': '2018-06-24T00:40:54.970+0000' - }, - { - 'name': 'lease', - 'hasEndDate': true, - 'maxEndDate': '2017-12-24T00:40:54.970+0000' - } - ], - 'bitstreamAccessConditionOptions': [ - { - 'name': 'openaccess' - }, - { - 'name': 'administrator' - }, - { - 'name': 'embargo', - 'hasStartDate': true, - 'maxStartDate': '2018-06-24T00:40:54.970+0000' - }, - { - 'name': 'lease', - 'hasEndDate': true, - 'maxEndDate': '2017-12-24T00:40:54.970+0000' - } - ] -}; diff --git a/src/app/core/data/bulk-access-condition-options.service.ts b/src/app/core/config/bulk-access-config-data.service.ts similarity index 53% rename from src/app/core/data/bulk-access-condition-options.service.ts rename to src/app/core/config/bulk-access-config-data.service.ts index 30e0402dbf..f27b59eee1 100644 --- a/src/app/core/data/bulk-access-condition-options.service.ts +++ b/src/app/core/config/bulk-access-config-data.service.ts @@ -3,16 +3,16 @@ import { Observable } from 'rxjs'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { RemoteData } from './remote-data'; -import { RequestService } from './request.service'; -import { IdentifiableDataService } from './base/identifiable-data.service'; -import { BulkAccessConditionOptions } from '../shared/bulk-access-condition-options.model'; +import { RemoteData } from '../data/remote-data'; +import { RequestService } from '../data/request.service'; +import { BulkAccessConditionOptions } from './models/bulk-access-condition-options.model'; +import { ConfigDataService } from './config-data.service'; @Injectable({ providedIn: 'root' }) /** * Data Service responsible for retrieving Bulk Access Condition Options from the REST API */ -export class BulkAccessConditionOptionsService extends IdentifiableDataService { +export class BulkAccessConfigDataService extends ConfigDataService { constructor( protected requestService: RequestService, @@ -23,11 +23,7 @@ export class BulkAccessConditionOptionsService extends IdentifiableDataService> { - return this.findByHref(this.halService.getEndpoint(this.linkPath)); + findByPropertyName(name: string): Observable> { + return this.findById(name) as Observable>; } - - // findByPropertyName(name: string): Observable> { - // return this.findById(name); - // } } diff --git a/src/app/core/config/config-data.service.ts b/src/app/core/config/config-data.service.ts index 9ef2f11ad1..f8f46435c5 100644 --- a/src/app/core/config/config-data.service.ts +++ b/src/app/core/config/config-data.service.ts @@ -4,13 +4,13 @@ import { RemoteData } from '../data/remote-data'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; import { map } from 'rxjs/operators'; -import { BaseDataService } from '../data/base/base-data.service'; +import { IdentifiableDataService } from '../data/base/identifiable-data.service'; /** * Abstract data service to retrieve configuration objects from the REST server. * Common logic for configuration objects should be implemented here. */ -export abstract class ConfigDataService extends BaseDataService { +export abstract class ConfigDataService extends IdentifiableDataService { /** * Returns an observable of {@link RemoteData} of an object, based on an href, with a list of * {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object @@ -37,4 +37,8 @@ export abstract class ConfigDataService extends BaseDataService { }), ); } + + findByName(name: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable> { + return super.findById(name, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } } diff --git a/src/app/core/config/models/bulk-access-condition-options.model.ts b/src/app/core/config/models/bulk-access-condition-options.model.ts new file mode 100644 index 0000000000..f10965ea31 --- /dev/null +++ b/src/app/core/config/models/bulk-access-condition-options.model.ts @@ -0,0 +1,38 @@ +import { autoserialize, autoserializeAs, deserialize } from 'cerialize'; +import { typedObject } from '../../cache/builders/build-decorators'; +import { excludeFromEquals } from '../../utilities/equals.decorators'; +import { ResourceType } from '../../shared/resource-type'; +import { HALLink } from '../../shared/hal-link.model'; +import { ConfigObject } from './config.model'; +import { AccessesConditionOption } from './config-accesses-conditions-options.model'; + +export const BULK_ACCESS_CONDITION_OPTIONS = new ResourceType('bulkAccessConditionOptions'); + +/** + * Model class for a bulk access condition options + */ +@typedObject +export class BulkAccessConditionOptions extends ConfigObject { + static type = BULK_ACCESS_CONDITION_OPTIONS; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + @autoserializeAs(String, 'name') + uuid: string; + + @autoserialize + id: string; + + @deserialize + itemAccessConditionOptions: AccessesConditionOption[]; + + @deserialize + bitstreamAccessConditionOptions: AccessesConditionOption[]; + + _links: { self: HALLink }; +} diff --git a/src/app/core/shared/bulk-access-condition-options.model.ts b/src/app/core/shared/bulk-access-condition-options.model.ts deleted file mode 100644 index a1c8fe0702..0000000000 --- a/src/app/core/shared/bulk-access-condition-options.model.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { autoserialize, autoserializeAs, deserialize } from 'cerialize'; -import { typedObject } from '../cache/builders/build-decorators'; -import { excludeFromEquals } from '../utilities/equals.decorators'; -import { ResourceType } from './resource-type'; -import { CacheableObject } from '../cache/cacheable-object.model'; -import { HALLink } from './hal-link.model'; - -export const BULK_ACCESS_CONDITION_OPTIONS = new ResourceType('bulkAccessConditionOptions'); - -/** - * Model class for a bulk access condition options - */ -@typedObject -export class BulkAccessConditionOptions implements CacheableObject { - static type = BULK_ACCESS_CONDITION_OPTIONS; - - /** - * The object type - */ - @excludeFromEquals - @autoserialize - type: ResourceType; - - @autoserializeAs(String, 'name') - uuid: string; - - @autoserialize - id: string; - - @deserialize - itemAccessConditionOptions: AccessControlItem[]; - - @deserialize - bitstreamAccessConditionOptions: AccessControlItem[]; - - _links: { self: HALLink }; -} - -export interface AccessControlItem { - name: string - hasStartDate?: boolean - maxStartDate?: string - hasEndDate?: boolean - maxEndDate?: string -} diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-array-form/access-control-array-form.component.html index 24dcc11120..5d0fb6d97e 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.html @@ -7,7 +7,7 @@
    @@ -20,7 +20,7 @@ placeholder="yyyy-mm-dd" name="dp" formControlName="startDate" - [minDate]="control | maxStartDate: dropdownOptions" + [minDate]="control | maxStartDate: (dropdownData$ | async)" ngbDatepicker #d="ngbDatepicker" /> @@ -40,7 +40,7 @@ placeholder="yyyy-mm-dd" name="dp" formControlName="endDate" - [maxDate]="control | maxEndDate: dropdownOptions" + [maxDate]="control | maxEndDate: (dropdownData$ | async)" ngbDatepicker #d1="ngbDatepicker" /> diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-array-form/access-control-array-form.component.ts index ace507a3a6..13d13375b2 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, NgModule, OnDestroy, OnInit } from '@angular/core'; +import { Component, NgModule, OnDestroy, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormArray, FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms'; import { SharedBrowseByModule } from '../browse-by/shared-browse-by.module'; @@ -6,9 +6,13 @@ import { TranslateModule } from '@ngx-translate/core'; import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; import { ControlMaxStartDatePipe } from './control-max-start-date.pipe'; import { ControlMaxEndDatePipe } from './control-max-end-date.pipe'; -import { AccessControlItem } from '../../core/shared/bulk-access-condition-options.model'; -import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; + +import { distinctUntilChanged, map, shareReplay, takeUntil, tap } from 'rxjs/operators'; import { Subject } from 'rxjs'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; +import { BulkAccessConditionOptions } from '../../core/config/models/bulk-access-condition-options.model'; +import { BulkAccessConfigDataService } from '../../core/config/bulk-access-config-data.service'; // will be used on the form value @@ -25,8 +29,6 @@ export interface AccessControlItemValue { exportAs: 'accessControlArrayForm' }) export class AccessControlArrayFormComponent implements OnInit, OnDestroy { - @Input() dropdownOptions: AccessControlItem[] = []; - @Input() accessControlItems: AccessControlItemValue[] = []; private destroy$ = new Subject(); @@ -34,17 +36,28 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { accessControl: this.fb.array([]) }); - constructor(private fb: FormBuilder) { + constructor(private bulkAccessConfigService: BulkAccessConfigDataService, + private fb: FormBuilder) { } + dropdownData$ = this.bulkAccessConfigService.findByPropertyName('default').pipe( + getFirstCompletedRemoteData(), + map((configRD: RemoteData) => configRD.hasSucceeded ? configRD.payload : null), + shareReplay(1), + tap(console.log) + ); + ngOnInit(): void { - if (this.accessControlItems.length === 0) { + // console.log(this.dropdownOptions); +/* if (this.accessControlItems.length === 0) { this.addAccessControlItem(); } else { for (const item of this.accessControlItems) { this.addAccessControlItem(item.itemName); } - } + }*/ + + this.addAccessControlItem(); this.accessControl.valueChanges .pipe( diff --git a/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts b/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts index 810f3e71d0..86ba3b996a 100644 --- a/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts +++ b/src/app/shared/access-control-array-form/control-max-end-date.pipe.ts @@ -1,7 +1,7 @@ import { Pipe, PipeTransform } from '@angular/core'; import { AbstractControl } from '@angular/forms'; import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap/datepicker/ngb-date-struct'; -import { AccessControlItem } from 'src/app/core/shared/bulk-access-condition-options.model'; +import { AccessesConditionOption } from '../../core/config/models/config-accesses-conditions-options.model'; @Pipe({ // eslint-disable-next-line @angular-eslint/pipe-prefix @@ -9,7 +9,7 @@ import { AccessControlItem } from 'src/app/core/shared/bulk-access-condition-opt pure: false }) export class ControlMaxEndDatePipe implements PipeTransform { - transform(control: AbstractControl, dropdownOptions: AccessControlItem[]): NgbDateStruct | null { + transform(control: AbstractControl, dropdownOptions: AccessesConditionOption[]): NgbDateStruct | null { const { itemName } = control.value; const item = dropdownOptions.find((x) => x.name === itemName); if (!item?.hasEndDate) { diff --git a/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts b/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts index 458cb36465..77be77b029 100644 --- a/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts +++ b/src/app/shared/access-control-array-form/control-max-start-date.pipe.ts @@ -1,7 +1,8 @@ import { Pipe, PipeTransform } from '@angular/core'; import { AbstractControl } from '@angular/forms'; import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap/datepicker/ngb-date-struct'; -import { AccessControlItem } from 'src/app/core/shared/bulk-access-condition-options.model'; +import { AccessesConditionOption } from '../../core/config/models/config-accesses-conditions-options.model'; + @Pipe({ // eslint-disable-next-line @angular-eslint/pipe-prefix @@ -9,7 +10,7 @@ import { AccessControlItem } from 'src/app/core/shared/bulk-access-condition-opt pure: false }) export class ControlMaxStartDatePipe implements PipeTransform { - transform(control: AbstractControl, dropdownOptions: AccessControlItem[]): NgbDateStruct | null { + transform(control: AbstractControl, dropdownOptions: AccessesConditionOption[]): NgbDateStruct | null { const { itemName } = control.value; const item = dropdownOptions.find((x) => x.name === itemName); if (!item?.hasStartDate) { diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.html b/src/app/shared/access-control-form-container/access-control-form-container.component.html index 71175ce588..70f1b1e6cd 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.html +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.html @@ -48,10 +48,7 @@
    - - +
    @@ -137,10 +134,7 @@
    - - +
    diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index a2e9cb08ab..0807236796 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectorRef, Component, Input, NgModule, ViewChild } from '@angular/core'; -import { concatMap, shareReplay } from 'rxjs'; +import { concatMap } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { @@ -20,6 +20,7 @@ import { ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID, ItemAccessControlSelectBitstreamsModalComponent } from './item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; +import { BulkAccessConfigDataService } from '../../core/config/bulk-access-config-data.service'; @Component({ selector: 'ds-access-control-form-container', @@ -27,7 +28,7 @@ import { styleUrls: [ './access-control-form-container.component.scss' ], exportAs: 'dsAccessControlForm' }) -export class AccessControlFormContainerComponent { +export class AccessControlFormContainerComponent implements OnInit { @Input() showLimitToSpecificBitstreams = false; @Input() itemRD: RemoteData; @@ -38,6 +39,7 @@ export class AccessControlFormContainerComponent { @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; constructor( + private bulkAccessConfigService: BulkAccessConfigDataService, private bulkAccessControlService: BulkAccessControlService, private selectableListService: SelectableListService, protected modalService: NgbModal, @@ -46,9 +48,16 @@ export class AccessControlFormContainerComponent { state = initialState; - dropdownData$ = this.bulkAccessControlService.dropdownData$.pipe( +/* dropdownData$ = this.bulkAccessConfigService.findByPropertyName('default').pipe( + getFirstCompletedRemoteData(), + map((configRD: RemoteData) => configRD.hasSucceeded ? configRD.payload : null), + shareReplay(1), + tap(console.log) + );*/ + +/* dropdownData$ = this.bulkAccessControlService.dropdownData$.pipe( shareReplay(1) - ); + );*/ getFormValue() { return { diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 85ec05cd60..1cce44d828 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -1,21 +1,12 @@ import { Injectable } from '@angular/core'; -import { Observable, of } from 'rxjs'; -import { AccessControlItem } from '../../core/shared/bulk-access-condition-options.model'; + import { ScriptDataService } from '../../core/data/processes/script-data.service'; import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; -export interface AccessControlDropdownDataResponse { - id: string; - itemAccessConditionOptions: AccessControlItem[]; - bitstreamAccessConditionOptions: AccessControlItem[]; -} - @Injectable({ providedIn: 'root' }) export class BulkAccessControlService { constructor(private scriptService: ScriptDataService) {} - dropdownData$: Observable = of(accessControlDropdownData); - createPayloadFile(payload: any) { console.log('execute', payload); @@ -43,43 +34,3 @@ export class BulkAccessControlService { return this.scriptService.invoke('bulk-access-control', params, [file]); } } - -const accessControlDropdownData: AccessControlDropdownDataResponse = { - 'id': 'default', - 'itemAccessConditionOptions': [ - { - 'name': 'openaccess' - }, - { - 'name': 'administrator' - }, - { - 'name': 'embargo', - 'hasStartDate': true, - 'maxStartDate': '2023-05-12T00:40:54.970+0000' - }, - { - 'name': 'lease', - 'hasEndDate': true, - 'maxEndDate': '2017-12-24T00:40:54.970+0000' - } - ], - 'bitstreamAccessConditionOptions': [ - { - 'name': 'openaccess' - }, - { - 'name': 'administrator' - }, - { - 'name': 'embargo', - 'hasStartDate': true, - 'maxStartDate': '2018-06-24T00:40:54.970+0000' - }, - { - 'name': 'lease', - 'hasEndDate': true, - 'maxEndDate': '2017-12-24T00:40:54.970+0000' - } - ] -}; From 4bf10c880a4b4ace19b1ea692fc85f18981ed512 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 10 May 2023 16:02:01 +0200 Subject: [PATCH 14/45] [CST-9636] Refactor access control array form to accept dropdown options --- .../access-control-array-form.component.html | 6 +-- .../access-control-array-form.component.ts | 37 +++---------------- ...cess-control-form-container.component.html | 18 +++++---- ...access-control-form-container.component.ts | 20 +++++----- 4 files changed, 28 insertions(+), 53 deletions(-) diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-array-form/access-control-array-form.component.html index 5d0fb6d97e..24dcc11120 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.html @@ -7,7 +7,7 @@
    @@ -20,7 +20,7 @@ placeholder="yyyy-mm-dd" name="dp" formControlName="startDate" - [minDate]="control | maxStartDate: (dropdownData$ | async)" + [minDate]="control | maxStartDate: dropdownOptions" ngbDatepicker #d="ngbDatepicker" /> @@ -40,7 +40,7 @@ placeholder="yyyy-mm-dd" name="dp" formControlName="endDate" - [maxDate]="control | maxEndDate: (dropdownData$ | async)" + [maxDate]="control | maxEndDate: dropdownOptions" ngbDatepicker #d1="ngbDatepicker" /> diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-array-form/access-control-array-form.component.ts index 13d13375b2..090b22d94c 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.ts @@ -1,4 +1,4 @@ -import { Component, NgModule, OnDestroy, OnInit } from '@angular/core'; +import { Component, Input, NgModule, OnDestroy, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormArray, FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms'; import { SharedBrowseByModule } from '../browse-by/shared-browse-by.module'; @@ -7,21 +7,11 @@ import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; import { ControlMaxStartDatePipe } from './control-max-start-date.pipe'; import { ControlMaxEndDatePipe } from './control-max-end-date.pipe'; -import { distinctUntilChanged, map, shareReplay, takeUntil, tap } from 'rxjs/operators'; +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; -import { getFirstCompletedRemoteData } from '../../core/shared/operators'; -import { RemoteData } from '../../core/data/remote-data'; -import { BulkAccessConditionOptions } from '../../core/config/models/bulk-access-condition-options.model'; -import { BulkAccessConfigDataService } from '../../core/config/bulk-access-config-data.service'; +import { AccessesConditionOption } from '../../core/config/models/config-accesses-conditions-options.model'; -// will be used on the form value -export interface AccessControlItemValue { - itemName: string | null; // item name - startDate?: string; - endDate?: string; -} - @Component({ selector: 'ds-access-control-array-form', templateUrl: './access-control-array-form.component.html', @@ -29,6 +19,7 @@ export interface AccessControlItemValue { exportAs: 'accessControlArrayForm' }) export class AccessControlArrayFormComponent implements OnInit, OnDestroy { + @Input() dropdownOptions: AccessesConditionOption[] = []; private destroy$ = new Subject(); @@ -36,27 +27,9 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { accessControl: this.fb.array([]) }); - constructor(private bulkAccessConfigService: BulkAccessConfigDataService, - private fb: FormBuilder) { - } - - dropdownData$ = this.bulkAccessConfigService.findByPropertyName('default').pipe( - getFirstCompletedRemoteData(), - map((configRD: RemoteData) => configRD.hasSucceeded ? configRD.payload : null), - shareReplay(1), - tap(console.log) - ); + constructor(private fb: FormBuilder) {} ngOnInit(): void { - // console.log(this.dropdownOptions); -/* if (this.accessControlItems.length === 0) { - this.addAccessControlItem(); - } else { - for (const item of this.accessControlItems) { - this.addAccessControlItem(item.itemName); - } - }*/ - this.addAccessControlItem(); this.accessControl.valueChanges diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.html b/src/app/shared/access-control-form-container/access-control-form-container.component.html index 70f1b1e6cd..c45774dcf9 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.html +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.html @@ -48,7 +48,10 @@
    - + +
    @@ -125,23 +128,24 @@
    -
    - {{'access-control-access-conditions' | translate}} -
    +
    {{'access-control-access-conditions' | translate}}
    {{'access-control-no-access-conditions-warning-message' | translate}}
    - + + -
    +
    -
    +
    diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 0807236796..9efc90a3f2 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectorRef, Component, Input, NgModule, ViewChild } from '@angular/core'; -import { concatMap } from 'rxjs'; +import { concatMap, Observable, shareReplay } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { @@ -9,7 +9,7 @@ import { import { BulkAccessControlService } from './bulk-access-control.service'; import { SelectableListService } from '../object-list/selectable-list/selectable-list.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { take } from 'rxjs/operators'; +import { map, take, tap } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; import { ListableObject } from '../object-collection/shared/listable-object.model'; import { SharedModule } from '../shared.module'; @@ -21,6 +21,8 @@ import { ItemAccessControlSelectBitstreamsModalComponent } from './item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; import { BulkAccessConfigDataService } from '../../core/config/bulk-access-config-data.service'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { BulkAccessConditionOptions } from '../../core/config/models/bulk-access-condition-options.model'; @Component({ selector: 'ds-access-control-form-container', @@ -28,12 +30,12 @@ import { BulkAccessConfigDataService } from '../../core/config/bulk-access-confi styleUrls: [ './access-control-form-container.component.scss' ], exportAs: 'dsAccessControlForm' }) -export class AccessControlFormContainerComponent implements OnInit { +export class AccessControlFormContainerComponent { @Input() showLimitToSpecificBitstreams = false; @Input() itemRD: RemoteData; - @Input() hideSubmit = false; + @Input() showSubmit = true; @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; @@ -48,16 +50,12 @@ export class AccessControlFormContainerComponent impleme state = initialState; -/* dropdownData$ = this.bulkAccessConfigService.findByPropertyName('default').pipe( + dropdownData$: Observable = this.bulkAccessConfigService.findByPropertyName('default').pipe( getFirstCompletedRemoteData(), map((configRD: RemoteData) => configRD.hasSucceeded ? configRD.payload : null), shareReplay(1), - tap(console.log) - );*/ - -/* dropdownData$ = this.bulkAccessControlService.dropdownData$.pipe( - shareReplay(1) - );*/ + tap(x => console.log('options', x)) + ); getFormValue() { return { From b279b97c37b4e6682f89d71fdaf9ecc9a2de3893 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 10 May 2023 16:23:27 +0200 Subject: [PATCH 15/45] [CST-9636] WIP Fix bulk-access-config-data.service --- .../config/bulk-access-config-data.service.ts | 11 ++++------- src/app/core/config/config-data.service.ts | 18 ++++++++++++++++-- .../bulk-access-condition-options.model.ts | 10 +++++----- src/app/core/config/models/config-type.ts | 2 ++ src/app/core/core.module.ts | 2 ++ .../access-control-form-container.component.ts | 7 +++---- 6 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/app/core/config/bulk-access-config-data.service.ts b/src/app/core/config/bulk-access-config-data.service.ts index f27b59eee1..28b4029ea2 100644 --- a/src/app/core/config/bulk-access-config-data.service.ts +++ b/src/app/core/config/bulk-access-config-data.service.ts @@ -1,17 +1,17 @@ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { RemoteData } from '../data/remote-data'; import { RequestService } from '../data/request.service'; -import { BulkAccessConditionOptions } from './models/bulk-access-condition-options.model'; import { ConfigDataService } from './config-data.service'; +import { dataService } from '../data/base/data-service.decorator'; +import { BULK_ACCESS_CONDITION_OPTIONS } from './models/config-type'; -@Injectable({ providedIn: 'root' }) /** * Data Service responsible for retrieving Bulk Access Condition Options from the REST API */ +@Injectable({ providedIn: 'root' }) +@dataService(BULK_ACCESS_CONDITION_OPTIONS) export class BulkAccessConfigDataService extends ConfigDataService { constructor( @@ -23,7 +23,4 @@ export class BulkAccessConfigDataService extends ConfigDataService { super('bulkaccessconditionoptions', requestService, rdbService, objectCache, halService); } - findByPropertyName(name: string): Observable> { - return this.findById(name) as Observable>; - } } diff --git a/src/app/core/config/config-data.service.ts b/src/app/core/config/config-data.service.ts index f8f46435c5..58b023e62c 100644 --- a/src/app/core/config/config-data.service.ts +++ b/src/app/core/config/config-data.service.ts @@ -1,9 +1,10 @@ import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + import { ConfigObject } from './models/config.model'; import { RemoteData } from '../data/remote-data'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; -import { map } from 'rxjs/operators'; import { IdentifiableDataService } from '../data/base/identifiable-data.service'; /** @@ -38,7 +39,20 @@ export abstract class ConfigDataService extends IdentifiableDataService[]): Observable> { + /** + * Returns a config object by given name + * + * Throws an error if a configuration object cannot be retrieved. + * + * @param name The name of configuration to retrieve + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + */ + public findByName(name: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable> { return super.findById(name, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } } diff --git a/src/app/core/config/models/bulk-access-condition-options.model.ts b/src/app/core/config/models/bulk-access-condition-options.model.ts index f10965ea31..d84e14b95d 100644 --- a/src/app/core/config/models/bulk-access-condition-options.model.ts +++ b/src/app/core/config/models/bulk-access-condition-options.model.ts @@ -1,17 +1,17 @@ -import { autoserialize, autoserializeAs, deserialize } from 'cerialize'; +import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; import { typedObject } from '../../cache/builders/build-decorators'; import { excludeFromEquals } from '../../utilities/equals.decorators'; import { ResourceType } from '../../shared/resource-type'; import { HALLink } from '../../shared/hal-link.model'; import { ConfigObject } from './config.model'; import { AccessesConditionOption } from './config-accesses-conditions-options.model'; - -export const BULK_ACCESS_CONDITION_OPTIONS = new ResourceType('bulkAccessConditionOptions'); +import { BULK_ACCESS_CONDITION_OPTIONS } from './config-type'; /** * Model class for a bulk access condition options */ @typedObject +@inheritSerialization(ConfigObject) export class BulkAccessConditionOptions extends ConfigObject { static type = BULK_ACCESS_CONDITION_OPTIONS; @@ -28,10 +28,10 @@ export class BulkAccessConditionOptions extends ConfigObject { @autoserialize id: string; - @deserialize + @autoserialize itemAccessConditionOptions: AccessesConditionOption[]; - @deserialize + @autoserialize bitstreamAccessConditionOptions: AccessesConditionOption[]; _links: { self: HALLink }; diff --git a/src/app/core/config/models/config-type.ts b/src/app/core/config/models/config-type.ts index 19864536f0..5733f5bfad 100644 --- a/src/app/core/config/models/config-type.ts +++ b/src/app/core/config/models/config-type.ts @@ -17,3 +17,5 @@ export const SUBMISSION_UPLOADS_TYPE = new ResourceType('submissionuploads'); export const SUBMISSION_UPLOAD_TYPE = new ResourceType('submissionupload'); export const SUBMISSION_ACCESSES_TYPE = new ResourceType('submissionaccessoption'); + +export const BULK_ACCESS_CONDITION_OPTIONS = new ResourceType('bulkaccessconditionoption'); diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 319b42d58b..7d540ccfcf 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -177,6 +177,7 @@ import { IdentifierData } from '../shared/object-list/identifier-data/identifier import { Subscription } from '../shared/subscriptions/models/subscription.model'; import { SupervisionOrderDataService } from './supervision-order/supervision-order-data.service'; import { ItemRequest } from './shared/item-request.model'; +import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -371,6 +372,7 @@ export const models = IdentifierData, Subscription, ItemRequest, + BulkAccessConditionOptions ]; @NgModule({ diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 9efc90a3f2..d09911a9d6 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -9,7 +9,7 @@ import { import { BulkAccessControlService } from './bulk-access-control.service'; import { SelectableListService } from '../object-list/selectable-list/selectable-list.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { map, take, tap } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; import { ListableObject } from '../object-collection/shared/listable-object.model'; import { SharedModule } from '../shared.module'; @@ -50,11 +50,10 @@ export class AccessControlFormContainerComponent { state = initialState; - dropdownData$: Observable = this.bulkAccessConfigService.findByPropertyName('default').pipe( + dropdownData$: Observable = this.bulkAccessConfigService.findByName('default').pipe( getFirstCompletedRemoteData(), map((configRD: RemoteData) => configRD.hasSucceeded ? configRD.payload : null), - shareReplay(1), - tap(x => console.log('options', x)) + shareReplay(1) ); getFormValue() { From 5f74446bf50ac07a3e6ece2b6cf437a2d733f6da Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 10 May 2023 16:27:53 +0200 Subject: [PATCH 16/45] [CST-9636] Added jsdoc comments --- ...ccess-control-array-form.component.spec.ts | 8 +- .../access-control-array-form.component.ts | 126 ++++++++++++------ ...s-control-form-container.component.spec.ts | 4 +- ...access-control-form-container.component.ts | 39 +++++- 4 files changed, 124 insertions(+), 53 deletions(-) diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.spec.ts b/src/app/shared/access-control-array-form/access-control-array-form.component.spec.ts index 7c3ed06be1..b99a0fff8e 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.spec.ts +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AccessControlArrayFormComponent, AccessControlItemValue } from './access-control-array-form.component'; +import { AccessControlArrayFormComponent } from './access-control-array-form.component'; import { ReactiveFormsModule } from '@angular/forms'; import { SharedBrowseByModule } from '../browse-by/shared-browse-by.module'; import { CommonModule } from '@angular/common'; @@ -26,7 +26,7 @@ fdescribe('AccessControlArrayFormComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(AccessControlArrayFormComponent); component = fixture.componentInstance; - component.dropdownOptions = [{name: 'Option1'}, {name: 'Option2'}]; + component.dropdownOptions = [{name: 'Option1'}, {name: 'Option2'}] as any; fixture.detectChanges(); }); @@ -54,14 +54,14 @@ fdescribe('AccessControlArrayFormComponent', () => { }); it('should set access control item value', () => { - const item: AccessControlItemValue = { itemName: 'item1', startDate: '2022-01-01', endDate: '2022-02-01' }; + const item = { itemName: 'item1', startDate: '2022-01-01', endDate: '2022-02-01' }; component.addAccessControlItem(item.itemName); component.accessControl.controls[0].patchValue(item); expect(component.form.value.accessControl[0]).toEqual(item); }); it('should reset form value', () => { - const item: AccessControlItemValue = { itemName: 'item1', startDate: '2022-01-01', endDate: '2022-02-01' }; + const item = { itemName: 'item1', startDate: '2022-01-01', endDate: '2022-02-01' }; component.addAccessControlItem(item.itemName); component.accessControl.controls[1].patchValue(item); component.reset(); diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-array-form/access-control-array-form.component.ts index 090b22d94c..60060dec16 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-array-form/access-control-array-form.component.ts @@ -31,7 +31,89 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { ngOnInit(): void { this.addAccessControlItem(); + this.handleValidationOnFormArrayChanges(); + } + /** + * Get the access control form array. + */ + get accessControl() { + return this.form.get('accessControl') as FormArray; + } + + /** + * Add a new access control item to the form. + * Start and end date are disabled by default. + * @param itemName The name of the item to add + */ + addAccessControlItem(itemName: string = null) { + this.accessControl.push(this.fb.group({ + itemName, + startDate: new FormControl({ value: null, disabled: true }), + endDate: new FormControl({ value: null, disabled: true }) + })); + } + + /** + * Remove an access control item from the form. + * @param index + */ + removeAccessControlItem(index: number) { + this.accessControl.removeAt(index); + } + + /** + * Get the value of the form. + * This will be used to read the form value from the parent component. + * @return The form value + */ + getValue() { + return this.form.value; + } + + /** + * Set the value of the form from the parent component. + */ + reset() { + this.accessControl.reset([]); + } + + /** + * Disable the form. + * This will be used to disable the form from the parent component. + * This will also disable all date controls. + */ + disable() { + this.form.disable(); + + // disable all date controls + for (const control of this.accessControl.controls) { + control.get('startDate').disable(); + control.get('endDate').disable(); + } + } + + /** + * Enable the form. + * This will be used to enable the form from the parent component. + * This will also enable all date controls. + */ + enable() { + this.form.enable(); + + // enable date controls + for (const control of this.accessControl.controls) { + control.get('startDate').enable(); + control.get('endDate').enable(); + } + } + + /** + * Handle validation on form array changes. + * This will be used to enable/disable date controls based on the selected item. + * @private + */ + private handleValidationOnFormArrayChanges() { this.accessControl.valueChanges .pipe( distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), @@ -61,50 +143,6 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { }); } - get accessControl() { - return this.form.get('accessControl') as FormArray; - } - - addAccessControlItem(itemName: string = null) { - this.accessControl.push(this.fb.group({ - itemName, - startDate: new FormControl({ value: null, disabled: true }), - endDate: new FormControl({ value: null, disabled: true }) - })); - } - - removeAccessControlItem(index: number) { - this.accessControl.removeAt(index); - } - - getValue() { - return this.form.value; - } - - reset() { - this.accessControl.reset([]); - } - - disable() { - this.form.disable(); - - // disable all date controls - for (const control of this.accessControl.controls) { - control.get('startDate').disable(); - control.get('endDate').disable(); - } - } - - enable() { - this.form.enable(); - - // enable date controls - for (const control of this.accessControl.controls) { - control.get('startDate').enable(); - control.get('endDate').enable(); - } - } - ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts index 871e301209..7412b9569f 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts @@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AccessControlFormContainerComponent } from './access-control-form-container.component'; describe('AccessControlFormContainerComponent', () => { - let component: AccessControlFormContainerComponent; - let fixture: ComponentFixture; + let component: AccessControlFormContainerComponent; + let fixture: ComponentFixture>; beforeEach(async () => { await TestBed.configureTestingModule({ diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 9efc90a3f2..23ba64c4f6 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, Input, NgModule, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, NgModule, OnDestroy, ViewChild } from '@angular/core'; import { concatMap, Observable, shareReplay } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; @@ -30,11 +30,23 @@ import { BulkAccessConditionOptions } from '../../core/config/models/bulk-access styleUrls: [ './access-control-form-container.component.scss' ], exportAs: 'dsAccessControlForm' }) -export class AccessControlFormContainerComponent { +export class AccessControlFormContainerComponent implements OnDestroy { + /** + * Will be used to determine if we need to show the limit changes to specific bitstreams radio buttons + */ @Input() showLimitToSpecificBitstreams = false; + + /** + * The item to which the access control form applies + */ @Input() itemRD: RemoteData; + /** + * Whether to show the submit and cancel button + * We want to hide these buttons when the form is + * used in an accordion, and we want to show buttons somewhere else + */ @Input() showSubmit = true; @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; @@ -57,6 +69,9 @@ export class AccessControlFormContainerComponent { tap(x => console.log('options', x)) ); + /** + * Will be used from a parent component to read the value of the form + */ getFormValue() { return { bitstream: this.bitstreamAccessCmp.getValue(), @@ -65,12 +80,20 @@ export class AccessControlFormContainerComponent { }; } + /** + * Reset the form to its initial state + * This will also reset the state of the child components (bitstream and item access) + */ reset() { this.bitstreamAccessCmp.reset(); this.itemAccessCmp.reset(); this.state = initialState; } + /** + * Submit the form + * This will create a payload file and execute the script + */ submit() { const bitstreamAccess = this.bitstreamAccessCmp.getValue(); const itemAccess = this.itemAccessCmp.getValue(); @@ -89,6 +112,12 @@ export class AccessControlFormContainerComponent { }); } + /** + * Handle the status change of the access control form (item or bitstream) + * This will enable/disable the access control form for the item or bitstream + * @param type The type of the access control form (item or bitstream) + * @param active boolean indicating whether the access control form should be enabled or disabled + */ handleStatusChange(type: 'item' | 'bitstream', active: boolean) { if (type === 'bitstream') { active ? this.bitstreamAccessCmp.enable() : this.bitstreamAccessCmp.disable(); @@ -97,6 +126,11 @@ export class AccessControlFormContainerComponent { } } + /** + * Open the modal to select bitstreams for which to change the access control + * This will open the modal and pass the currently selected bitstreams + * @param item The item for which to change the access control + */ openSelectBitstreamsModal(item: Item) { const ref = this.modalService.open(ItemAccessControlSelectBitstreamsModalComponent); ref.componentInstance.selectedBitstreams = this.state.bitstream.selectedBitstreams; @@ -111,7 +145,6 @@ export class AccessControlFormContainerComponent { }); } - // eslint-disable-next-line @angular-eslint/use-lifecycle-interface ngOnDestroy(): void { this.selectableListService.deselectAll(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID); } From 628732b93238bceff518eea93f66872925e4d804 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 10 May 2023 16:45:17 +0200 Subject: [PATCH 17/45] [CST-9636] Show warning only when there are no controls and form is not disabled --- .../edit-collection-page.module.ts | 2 +- .../edit-community-page.module.ts | 2 +- .../edit-item-page/edit-item-page.module.ts | 4 ---- .../access-control-array-form.component.html | 4 ++++ .../access-control-array-form.component.scss | 0 .../access-control-array-form.component.spec.ts | 2 +- .../access-control-array-form.component.ts | 4 ++-- .../control-max-end-date.pipe.ts | 2 +- .../control-max-start-date.pipe.ts | 2 +- .../access-control-form-container.component.html | 15 ++------------- .../access-control-form-container.component.ts | 4 ++-- 11 files changed, 15 insertions(+), 26 deletions(-) rename src/app/shared/{ => access-control-form-container}/access-control-array-form/access-control-array-form.component.html (92%) rename src/app/shared/{ => access-control-form-container}/access-control-array-form/access-control-array-form.component.scss (100%) rename src/app/shared/{ => access-control-form-container}/access-control-array-form/access-control-array-form.component.spec.ts (98%) rename src/app/shared/{ => access-control-form-container}/access-control-array-form/access-control-array-form.component.ts (96%) rename src/app/shared/{ => access-control-form-container}/access-control-array-form/control-max-end-date.pipe.ts (87%) rename src/app/shared/{ => access-control-form-container}/access-control-array-form/control-max-start-date.pipe.ts (87%) diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts index 9d703c0cf4..58563f440b 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts @@ -16,7 +16,7 @@ import { ComcolModule } from '../../shared/comcol/comcol.module'; import { CollectionAccessControlComponent } from './collection-access-control/collection-access-control.component'; import { AccessControlArrayFormModule -} from '../../shared/access-control-array-form/access-control-array-form.component'; +} from '../../shared/access-control-form-container/access-control-array-form/access-control-array-form.component'; import { UiSwitchModule } from 'ngx-ui-switch'; import { AccessControlFormContainerModule diff --git a/src/app/community-page/edit-community-page/edit-community-page.module.ts b/src/app/community-page/edit-community-page/edit-community-page.module.ts index 5ed2e401c6..d88aadfd8e 100644 --- a/src/app/community-page/edit-community-page/edit-community-page.module.ts +++ b/src/app/community-page/edit-community-page/edit-community-page.module.ts @@ -14,7 +14,7 @@ import { CommunityAccessControlComponent } from './community-access-control/comm import { UiSwitchModule } from 'ngx-ui-switch'; import { AccessControlArrayFormModule -} from '../../shared/access-control-array-form/access-control-array-form.component'; +} from '../../shared/access-control-form-container/access-control-array-form/access-control-array-form.component'; import { AccessControlFormContainerModule } from '../../shared/access-control-form-container/access-control-form-container.component'; diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 322c6c6999..9d132abe53 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -47,9 +47,6 @@ import { IdentifierDataComponent } from '../../shared/object-list/identifier-dat import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component'; import { DsoSharedModule } from '../../dso-shared/dso-shared.module'; import { ItemAccessControlComponent } from './item-access-control/item-access-control.component'; -import { - AccessControlArrayFormModule -} from '../../shared/access-control-array-form/access-control-array-form.component'; import { UiSwitchModule } from 'ngx-ui-switch'; import { ResultsBackButtonModule } from '../../shared/results-back-button/results-back-button.module'; import { @@ -72,7 +69,6 @@ import { NgbModule, ItemVersionsModule, DsoSharedModule, - AccessControlArrayFormModule, UiSwitchModule, ResultsBackButtonModule, AccessControlFormContainerModule, diff --git a/src/app/shared/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html similarity index 92% rename from src/app/shared/access-control-array-form/access-control-array-form.component.html rename to src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index 24dcc11120..9b1f018ffb 100644 --- a/src/app/shared/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -1,4 +1,8 @@
    +
    + {{'access-control-no-access-conditions-warning-message' | translate}} +
    +
    -
    -
    {{'access-control-access-conditions' | translate}}
    -
    - {{'access-control-no-access-conditions-warning-message' | translate}} -
    -
    +
    {{'access-control-access-conditions' | translate}}
    -
    -
    {{'access-control-access-conditions' | translate}}
    - -
    - {{'access-control-no-access-conditions-warning-message' | translate}} -
    -
    +
    {{'access-control-access-conditions' | translate}}
    Date: Wed, 10 May 2023 17:12:53 +0200 Subject: [PATCH 18/45] [CST-9636] Added state transformer to file model, added tests for bulk access control service --- .../access-control-array-form.component.ts | 4 +- ...s-control-form-container.component.spec.ts | 63 +++++++++++-------- ...access-control-form-container.component.ts | 14 ++++- .../bulk-access-control.service.spec.ts | 51 +++++++++++++++ .../bulk-access-control.service.ts | 47 +++++++++++++- 5 files changed, 149 insertions(+), 30 deletions(-) create mode 100644 src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts index 171bec8395..41b51d73dc 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts @@ -68,7 +68,9 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { * @return The form value */ getValue() { - return this.form.value; + return this.form.value.accessControl + .filter(x => x.itemName !== null && x.itemName !== '') + .map(x => ({ name: x.itemName, startDate: x.startDate || null, endDate: x.endDate || null })); } /** diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts index 7412b9569f..66ef20444e 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts @@ -1,25 +1,38 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AccessControlFormContainerComponent } from './access-control-form-container.component'; - -describe('AccessControlFormContainerComponent', () => { - let component: AccessControlFormContainerComponent; - let fixture: ComponentFixture>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ AccessControlFormContainerComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(AccessControlFormContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); +// +// describe('AccessControlFormContainerComponent', () => { +// let component: AccessControlFormContainerComponent; +// let fixture: ComponentFixture>; +// +// let bulkAccessConfigDataServiceMock: BulkAccessConfigDataService; +// +// beforeEach(async () => { +// +// bulkAccessConfigDataServiceMock = jasmine.createSpyObj('BulkAccessConfigDataService', { +// findByName: jasmine.createSpy('findByName'), +// }); +// +// +// await TestBed.configureTestingModule({ +// declarations: [ AccessControlFormContainerComponent ], +// imports: [ CommonModule, ReactiveFormsModule, SharedBrowseByModule, TranslateModule, NgbDatepickerModule ], +// providers: [ +// { provide: BulkAccessConfigDataService, useValue: bulkAccessConfigDataServiceMock }, +// // private bulkAccessControlService: BulkAccessControlService, +// // private selectableListService: SelectableListService, +// // protected modalService: NgbModal, +// // private cdr: ChangeDetectorRef +// ] +// }) +// .compileComponents(); +// }); +// +// beforeEach(() => { +// fixture = TestBed.createComponent(AccessControlFormContainerComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); +// +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +// }); diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index eec4996073..e34928361f 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -150,7 +150,7 @@ export class AccessControlFormContainerComponent impleme } -const initialState = { +const initialState: AccessControlFormState = { item: { toggleStatus: false, accessMode: '', @@ -163,6 +163,18 @@ const initialState = { }, }; +export interface AccessControlFormState { + item: { + toggleStatus: boolean, + accessMode: string, + }, + bitstream: { + toggleStatus: boolean, + accessMode: string, + changesLimit: string, + selectedBitstreams: ListableObject[], + } +} @NgModule({ imports: [ diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts new file mode 100644 index 0000000000..9e733b5694 --- /dev/null +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts @@ -0,0 +1,51 @@ +import { TestBed } from '@angular/core/testing'; +import { BulkAccessControlService } from './bulk-access-control.service'; +import { ScriptDataService } from '../../core/data/processes/script-data.service'; +import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; + +fdescribe('BulkAccessControlService', () => { + let service: BulkAccessControlService; + let scriptServiceSpy: jasmine.SpyObj; + + beforeEach(() => { + const spy = jasmine.createSpyObj('ScriptDataService', ['invoke']); + TestBed.configureTestingModule({ + providers: [ + BulkAccessControlService, + { provide: ScriptDataService, useValue: spy } + ] + }); + service = TestBed.inject(BulkAccessControlService); + scriptServiceSpy = TestBed.inject(ScriptDataService) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('createPayloadFile', () => { + it('should create a file and return the URL and file object', () => { + const payload = { data: 'test' }; + const result = service.createPayloadFile(payload); + + expect(result.url).toBeTruthy(); + expect(result.file).toBeTruthy(); + }); + }); + + describe('executeScript', () => { + it('should invoke the script service with the correct parameters', () => { + const uuids = ['123', '456']; + const file = new File(['test'], 'data.json', { type: 'application/json' }); + const expectedParams: ProcessParameter[] = [{ name: 'uuid', value: '123,456' }]; + + // @ts-ignore + scriptServiceSpy.invoke.and.returnValue(Promise.resolve({})); + + const result = service.executeScript(uuids, file); + + expect(scriptServiceSpy.invoke).toHaveBeenCalledWith('bulk-access-control', expectedParams, [file]); + expect(result).toBeTruthy(); + }); + }); +}); diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 1cce44d828..f5afe338dd 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -2,15 +2,16 @@ import { Injectable } from '@angular/core'; import { ScriptDataService } from '../../core/data/processes/script-data.service'; import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; +import { AccessControlFormState } from './access-control-form-container.component'; @Injectable({ providedIn: 'root' }) export class BulkAccessControlService { constructor(private scriptService: ScriptDataService) {} - createPayloadFile(payload: any) { - console.log('execute', payload); + createPayloadFile(payload: { state: AccessControlFormState, bitstreamAccess, itemAccess }) { + const content = convertToBulkAccessControlFileModel(payload); - const blob = new Blob([JSON.stringify(payload, null, 2)], { + const blob = new Blob([JSON.stringify(content, null, 2)], { type: 'application/json', }); @@ -34,3 +35,43 @@ export class BulkAccessControlService { return this.scriptService.invoke('bulk-access-control', params, [file]); } } + +export const convertToBulkAccessControlFileModel = (payload: { state: AccessControlFormState, bitstreamAccess: AccessCondition[], itemAccess: AccessCondition[] }): BulkAccessControlFileModel => { + const constraints = { uuid: [] }; + + if (payload.state.bitstream.changesLimit === 'selected') { + // @ts-ignore + constraints.uuid = payload.state.bitstream.selectedBitstreams.map((x) => x.id); + } + + return { + item: { + mode: payload.state.item.accessMode, + accessConditions: payload.itemAccess + }, + bitstream: { + constraints, + mode: payload.state.bitstream.accessMode, + accessConditions: payload.bitstreamAccess + } + }; +}; + + +export interface BulkAccessControlFileModel { + item: { + mode: string; + accessConditions: AccessCondition[]; + }, + bitstream: { + constraints: { uuid: string[] }; + mode: string; + accessConditions: AccessCondition[]; + } +} + +interface AccessCondition { + name: string; + startDate?: string; + endDate?: string; +} From e31fc562c5ab4006ffb8a6220f6001a4b0fb929f Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 10 May 2023 18:25:41 +0200 Subject: [PATCH 19/45] [CST-9636] WIP Create the access-control.module --- .../edit-collection-page.module.ts | 16 +++------ .../community-access-control.component.ts | 2 -- .../edit-community-page.module.ts | 12 ++----- .../edit-item-page/edit-item-page.module.ts | 9 ++--- .../access-control-array-form.component.ts | 19 ++-------- ...access-control-form-container.component.ts | 29 ++------------- .../access-control-form.module.ts | 36 +++++++++++++++++++ 7 files changed, 51 insertions(+), 72 deletions(-) create mode 100644 src/app/shared/access-control-form-container/access-control-form.module.ts diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts index 58563f440b..8d0cb179f1 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts @@ -9,18 +9,14 @@ import { CollectionCurateComponent } from './collection-curate/collection-curate import { CollectionSourceComponent } from './collection-source/collection-source.component'; import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component'; import { CollectionFormModule } from '../collection-form/collection-form.module'; -import { CollectionSourceControlsComponent } from './collection-source/collection-source-controls/collection-source-controls.component'; +import { + CollectionSourceControlsComponent +} from './collection-source/collection-source-controls/collection-source-controls.component'; import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module'; import { FormModule } from '../../shared/form/form.module'; import { ComcolModule } from '../../shared/comcol/comcol.module'; import { CollectionAccessControlComponent } from './collection-access-control/collection-access-control.component'; -import { - AccessControlArrayFormModule -} from '../../shared/access-control-form-container/access-control-array-form/access-control-array-form.component'; -import { UiSwitchModule } from 'ngx-ui-switch'; -import { - AccessControlFormContainerModule -} from '../../shared/access-control-form-container/access-control-form-container.component'; +import { AccessControlFormModule } from '../../shared/access-control-form-container/access-control-form.module'; /** * Module that contains all components related to the Edit Collection page administrator functionality @@ -34,9 +30,7 @@ import { ResourcePoliciesModule, FormModule, ComcolModule, - AccessControlArrayFormModule, - UiSwitchModule, - AccessControlFormContainerModule, + AccessControlFormModule, ], declarations: [ EditCollectionPageComponent, diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts index b24069b9ac..8a216e38df 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.ts @@ -1,8 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; -import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ActivatedRoute } from '@angular/router'; import { map } from 'rxjs/operators'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; diff --git a/src/app/community-page/edit-community-page/edit-community-page.module.ts b/src/app/community-page/edit-community-page/edit-community-page.module.ts index d88aadfd8e..5190d6a008 100644 --- a/src/app/community-page/edit-community-page/edit-community-page.module.ts +++ b/src/app/community-page/edit-community-page/edit-community-page.module.ts @@ -11,13 +11,9 @@ import { CommunityFormModule } from '../community-form/community-form.module'; import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../shared/comcol/comcol.module'; import { CommunityAccessControlComponent } from './community-access-control/community-access-control.component'; -import { UiSwitchModule } from 'ngx-ui-switch'; import { - AccessControlArrayFormModule -} from '../../shared/access-control-form-container/access-control-array-form/access-control-array-form.component'; -import { - AccessControlFormContainerModule -} from '../../shared/access-control-form-container/access-control-form-container.component'; + AccessControlFormModule +} from '../../shared/access-control-form-container/access-control-form.module'; /** * Module that contains all components related to the Edit Community page administrator functionality @@ -30,9 +26,7 @@ import { CommunityFormModule, ComcolModule, ResourcePoliciesModule, - UiSwitchModule, - AccessControlArrayFormModule, - AccessControlFormContainerModule, + AccessControlFormModule, ], declarations: [ EditCommunityPageComponent, diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 9d132abe53..ef6abf4a84 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -47,12 +47,10 @@ import { IdentifierDataComponent } from '../../shared/object-list/identifier-dat import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component'; import { DsoSharedModule } from '../../dso-shared/dso-shared.module'; import { ItemAccessControlComponent } from './item-access-control/item-access-control.component'; -import { UiSwitchModule } from 'ngx-ui-switch'; import { ResultsBackButtonModule } from '../../shared/results-back-button/results-back-button.module'; import { - AccessControlFormContainerModule -} from '../../shared/access-control-form-container/access-control-form-container.component'; - + AccessControlFormModule +} from '../../shared/access-control-form-container/access-control-form.module'; /** * Module that contains all components related to the Edit Item page administrator functionality @@ -69,9 +67,8 @@ import { NgbModule, ItemVersionsModule, DsoSharedModule, - UiSwitchModule, ResultsBackButtonModule, - AccessControlFormContainerModule, + AccessControlFormModule, ], declarations: [ EditItemPageComponent, diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts index 41b51d73dc..1e978d9aa9 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts @@ -1,17 +1,10 @@ -import { Component, Input, NgModule, OnDestroy, OnInit } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormArray, FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms'; -import { SharedBrowseByModule } from '../../browse-by/shared-browse-by.module'; -import { TranslateModule } from '@ngx-translate/core'; -import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; -import { ControlMaxStartDatePipe } from './control-max-start-date.pipe'; -import { ControlMaxEndDatePipe } from './control-max-end-date.pipe'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { FormArray, FormBuilder, FormControl } from '@angular/forms'; import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; import { AccessesConditionOption } from '../../../core/config/models/config-accesses-conditions-options.model'; - @Component({ selector: 'ds-access-control-array-form', templateUrl: './access-control-array-form.component.html', @@ -151,11 +144,3 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { } } - -@NgModule({ - imports: [ CommonModule, ReactiveFormsModule, SharedBrowseByModule, TranslateModule, NgbDatepickerModule ], - declarations: [ AccessControlArrayFormComponent, ControlMaxStartDatePipe, ControlMaxEndDatePipe ], - exports: [ AccessControlArrayFormComponent ], -}) -export class AccessControlArrayFormModule { -} diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index e34928361f..10ce81d6fc 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -1,20 +1,13 @@ -import { ChangeDetectorRef, Component, Input, NgModule, OnDestroy, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnDestroy, ViewChild } from '@angular/core'; import { concatMap, Observable, shareReplay } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; -import { - AccessControlArrayFormComponent, - AccessControlArrayFormModule -} from './access-control-array-form/access-control-array-form.component'; +import { AccessControlArrayFormComponent } from './access-control-array-form/access-control-array-form.component'; import { BulkAccessControlService } from './bulk-access-control.service'; import { SelectableListService } from '../object-list/selectable-list/selectable-list.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { map, take } from 'rxjs/operators'; -import { CommonModule } from '@angular/common'; import { ListableObject } from '../object-collection/shared/listable-object.model'; -import { SharedModule } from '../shared.module'; -import { TranslateModule } from '@ngx-translate/core'; -import { UiSwitchModule } from 'ngx-ui-switch'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID, @@ -175,21 +168,3 @@ export interface AccessControlFormState { selectedBitstreams: ListableObject[], } } - -@NgModule({ - imports: [ - CommonModule, - AccessControlArrayFormModule, - SharedModule, - TranslateModule, - UiSwitchModule - ], - declarations: [ - AccessControlFormContainerComponent, - ItemAccessControlSelectBitstreamsModalComponent - ], - exports: [ AccessControlFormContainerComponent, AccessControlArrayFormModule ], -}) -export class AccessControlFormContainerModule {} - - diff --git a/src/app/shared/access-control-form-container/access-control-form.module.ts b/src/app/shared/access-control-form-container/access-control-form.module.ts new file mode 100644 index 0000000000..4bba26033f --- /dev/null +++ b/src/app/shared/access-control-form-container/access-control-form.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { TranslateModule } from '@ngx-translate/core'; +import { UiSwitchModule } from 'ngx-ui-switch'; + +import { + AccessControlArrayFormComponent +} from './access-control-array-form/access-control-array-form.component'; +import { SharedModule } from '../shared.module'; +import { + ItemAccessControlSelectBitstreamsModalComponent +} from './item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; +import { AccessControlFormContainerComponent } from './access-control-form-container.component'; +import { ControlMaxStartDatePipe } from './access-control-array-form/control-max-start-date.pipe'; +import { ControlMaxEndDatePipe } from './access-control-array-form/control-max-end-date.pipe'; +import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; + +@NgModule({ + imports: [ + CommonModule, + SharedModule, + TranslateModule, + UiSwitchModule, + NgbDatepickerModule + ], + declarations: [ + AccessControlFormContainerComponent, + AccessControlArrayFormComponent, + ItemAccessControlSelectBitstreamsModalComponent, + ControlMaxStartDatePipe, + ControlMaxEndDatePipe + ], + exports: [ AccessControlFormContainerComponent, AccessControlArrayFormComponent ], +}) +export class AccessControlFormModule {} From 64c0fff37010897427ecd72323378050bf0858a6 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 10 May 2023 20:18:20 +0200 Subject: [PATCH 20/45] [CST-9636] WIP Create unit tests --- .../access-control/access-control.module.ts | 20 ++-- .../browse/bulk-access-browse.component.html | 3 - .../bulk-access-browse.component.spec.ts | 75 ++++++++++++-- .../browse/bulk-access-browse.component.ts | 7 +- .../bulk-access/bulk-access.component.html | 14 ++- .../bulk-access/bulk-access.component.spec.ts | 96 +++++++++++++++++- .../bulk-access/bulk-access.component.ts | 98 ++++++++++++++++++- .../bulk-access-settings.component.html | 2 +- .../bulk-access-settings.component.spec.ts | 65 +++++++++++- .../bulk-access-settings.component.ts | 30 ++++-- ...ccess-control-array-form.component.spec.ts | 2 +- ...access-control-form-container.component.ts | 5 + .../bulk-access-control.service.spec.ts | 8 +- .../bulk-access-control.service.ts | 8 +- .../selectable-list.service.spec.ts | 2 +- 15 files changed, 382 insertions(+), 53 deletions(-) diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts index ba7334d24f..3dc4b6cedc 100644 --- a/src/app/access-control/access-control.module.ts +++ b/src/app/access-control/access-control.module.ts @@ -17,6 +17,7 @@ import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; import { BulkAccessBrowseComponent } from './bulk-access/browse/bulk-access-browse.component'; import { BulkAccessSettingsComponent } from './bulk-access/settings/bulk-access-settings.component'; import { SearchModule } from '../shared/search/search.module'; +import { AccessControlFormModule } from '../shared/access-control-form-container/access-control-form.module'; /** * Condition for displaying error messages on email form field @@ -27,15 +28,16 @@ export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = }; @NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule, - AccessControlRoutingModule, - FormModule, - NgbAccordionModule, - SearchModule, - ], + imports: [ + CommonModule, + SharedModule, + RouterModule, + AccessControlRoutingModule, + FormModule, + NgbAccordionModule, + SearchModule, + AccessControlFormModule, + ], exports: [ MembersListComponent, ], diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html index 9851aab835..6ef45cdd5b 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html @@ -51,6 +51,3 @@ - - - diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts index 12cfabae08..c8379b06ee 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts @@ -1,25 +1,82 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +import { of } from 'rxjs'; +import { NgbAccordionModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; import { BulkAccessBrowseComponent } from './bulk-access-browse.component'; +import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; +import { SelectableObject } from '../../../shared/object-list/selectable-list/selectable-list.service.spec'; +import { PageInfo } from '../../../core/shared/page-info.model'; +import { buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; -describe('BrowseComponent', () => { +describe('BulkAccessBrowseComponent', () => { let component: BulkAccessBrowseComponent; let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ BulkAccessBrowseComponent ] - }) - .compileComponents(); - }); + const listID1 = 'id1'; + const value1 = 'Selected object'; + const value2 = 'Another selected object'; + + const selected1 = new SelectableObject(value1); + const selected2 = new SelectableObject(value2); + + const testSelection = { id: listID1, selection: [selected1, selected2] } ; + + const selectableListService = jasmine.createSpyObj('SelectableListService', ['getSelectableList', 'deselectAll']); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + NgbAccordionModule, + NgbNavModule, + TranslateModule.forRoot() + ], + declarations: [BulkAccessBrowseComponent], + providers: [ { provide: SelectableListService, useValue: selectableListService }, ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }).compileComponents(); + })); beforeEach(() => { fixture = TestBed.createComponent(BulkAccessBrowseComponent); component = fixture.componentInstance; + (component as any).selectableListService.getSelectableList.and.returnValue(of(testSelection)); fixture.detectChanges(); }); - it('should create', () => { + afterEach(() => { + fixture.destroy(); + component = null; + }); + + it('should create the component', () => { expect(component).toBeTruthy(); }); + + it('should have an initial active nav id of "search"', () => { + expect(component.activateId).toEqual('search'); + }); + + it('should have an initial pagination options object with default values', () => { + expect(component.paginationOptions$.getValue().id).toEqual('bas'); + expect(component.paginationOptions$.getValue().pageSize).toEqual(5); + expect(component.paginationOptions$.getValue().currentPage).toEqual(1); + }); + + it('should have an initial remote data with a paginated list as value', () => { + const list = buildPaginatedList(new PageInfo({ + "elementsPerPage": 5, + "totalElements": 2, + "totalPages": 1, + "currentPage": 1 + }), [selected1, selected2]) ; + const rd = createSuccessfulRemoteDataObject(list); + + expect(component.objectsSelected$.value).toEqual(rd); + }); + }); diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts index 7cbb8740a7..53a7302b46 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { BehaviorSubject, Subscription } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs/operators'; +import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; @@ -67,7 +67,8 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { this.subs.push( this.selectableListService.getSelectableList(this.listId).pipe( distinctUntilChanged(), - map((list: SelectableListState) => this.generatePaginatedListBySelectedElements(list)) + map((list: SelectableListState) => this.generatePaginatedListBySelectedElements(list)), + tap(console.log) ).subscribe(this.objectsSelected$) ) } @@ -76,14 +77,12 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { this.paginationOptions$.next(Object.assign(new PaginationComponentOptions(), this.paginationOptions$.value, { currentPage: this.paginationOptions$.value.currentPage + 1 })); - console.log(this.paginationOptions$.value); } pagePrev() { this.paginationOptions$.next(Object.assign(new PaginationComponentOptions(), this.paginationOptions$.value, { currentPage: this.paginationOptions$.value.currentPage - 1 })); - console.log(this.paginationOptions$.value); } private calculatePageCount(pageSize, totalCount = 0) { diff --git a/src/app/access-control/bulk-access/bulk-access.component.html b/src/app/access-control/bulk-access/bulk-access.component.html index 12ab88cd1a..aa6c82e133 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.html +++ b/src/app/access-control/bulk-access/bulk-access.component.html @@ -1,8 +1,18 @@ -
    - + + +
    + +
    + + +
    diff --git a/src/app/access-control/bulk-access/bulk-access.component.spec.ts b/src/app/access-control/bulk-access/bulk-access.component.spec.ts index 61fab1e5a9..3372f98dab 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.spec.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.spec.ts @@ -1,25 +1,115 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +import { TranslateModule } from '@ngx-translate/core'; +import { of } from 'rxjs'; import { BulkAccessComponent } from './bulk-access.component'; +import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; +import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; +import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { Process } from '../../process-page/processes/process.model'; -describe('BulkAccessComponent', () => { +fdescribe('BulkAccessComponent', () => { let component: BulkAccessComponent; let fixture: ComponentFixture; + let bulkAccessControlService: any; + let selectableListService: any; + + const selectableListServiceMock = jasmine.createSpyObj('SelectableListService', ['getSelectableList', 'deselectAll']); + const bulkAccessControlServiceMock = jasmine.createSpyObj('bulkAccessControlService', ['createPayloadFile', 'executeScript']); + + const mockFormState = { + "bitstream": [], + "item": [ + { + "name": "embargo", + "startDate": { + "year": 2026, + "month": 5, + "day": 31 + }, + "endDate": null + } + ], + "state": { + "item": { + "toggleStatus": true, + "accessMode": "replace" + }, + "bitstream": { + "toggleStatus": false, + "accessMode": "", + "changesLimit": "", + "selectedBitstreams": [] + } + } + }; + + const mockSettings: any = jasmine.createSpyObj('AccessControlFormContainerComponent', { + getValue: jasmine.createSpy('getValue'), + reset: jasmine.createSpy('reset') + }); + const selection: any[] = [{ indexableObject: { uuid: '1234' } }, { indexableObject: { uuid: '5678' } }]; + const selectableListState: SelectableListState = { id: 'test', selection }; + const expectedIdList = ['1234', '5678']; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ BulkAccessComponent ] + imports: [ TranslateModule.forRoot() ], + declarations: [ BulkAccessComponent ], + providers: [ + { provide: BulkAccessControlService, useValue: bulkAccessControlServiceMock }, + { provide: SelectableListService, useValue: selectableListServiceMock } + ], + schemas: [NO_ERRORS_SCHEMA] }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(BulkAccessComponent); component = fixture.componentInstance; + bulkAccessControlService = TestBed.inject(BulkAccessControlService); + selectableListService = TestBed.inject(SelectableListService); + (component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListState)); fixture.detectChanges(); + component.settings = mockSettings; + }); + + afterEach(() => { + fixture.destroy(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should generate the id list by selected elements', () => { + expect(component.objectsSelected$.value).toEqual(expectedIdList); + }); + + it('should disable the execute button when there are no objects selected', () => { + expect(component.canExport()).toBe(false); + }); + + it('should enable the execute button when there are objects selected', () => { + component.objectsSelected$.next(['1234']); + expect(component.canExport()).toBe(true); + }); + + it('should call the settings reset method when reset is called', () => { + spyOn(component.settings, 'reset'); + component.reset(); + expect(component.settings.reset).toHaveBeenCalled(); + }); + + it('should call the bulkAccessControlService executeScript method when submit is called', () => { + (component.settings as any).getValue.and.returnValue(mockFormState) + bulkAccessControlService.executeScript.and.returnValue(createSuccessfulRemoteDataObject$(new Process())); + component.objectsSelected$.next(['1234']); + component.submit(); + expect(bulkAccessControlService.executeScript).toHaveBeenCalled(); + }); }); diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts index bc0d293e61..8a82edb273 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -1,8 +1,31 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; + +import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.component'; +import { distinctUntilChanged, map, take, tap } from 'rxjs/operators'; +import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; +import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; +import { BehaviorSubject, Subscription } from 'rxjs'; +import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; @Component({ selector: 'ds-bulk-access', - templateUrl: './bulk-access.component.html', + // templateUrl: './bulk-access.component.html', + template: `
    + +
    + + +
    + +
    + + +
    +
    `, styleUrls: ['./bulk-access.component.scss'] }) export class BulkAccessComponent implements OnInit { @@ -13,9 +36,78 @@ export class BulkAccessComponent implements OnInit { */ listId: string = 'bulk-access-list'; - constructor() { } + /** + * The list of the objects already selected + */ + objectsSelected$: BehaviorSubject = new BehaviorSubject([]); + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + */ + private subs: Subscription[] = []; + + /** + * The SectionsDirective reference + */ + @ViewChild('dsBulkSettings') settings: BulkAccessSettingsComponent; + + constructor( + private bulkAccessControlService: BulkAccessControlService, + private selectableListService: SelectableListService + ) { } ngOnInit(): void { + this.subs.push( + this.selectableListService.getSelectableList(this.listId).pipe( + distinctUntilChanged(), + tap(console.log), + map((list: SelectableListState) => this.generateIdListBySelectedElements(list)), + tap(console.log) + ).subscribe(this.objectsSelected$) + ) } + canExport(): boolean { + return this.objectsSelected$.value?.length > 0; + } + + /** + * Reset the form to its initial state + * This will also reset the state of the child components (bitstream and item access) + */ + reset(): void { + this.settings.reset() + } + + /** + * Submit the form + * This will create a payload file and execute the script + */ + submit(): void { + const settings = this.settings.getValue(); + const bitstreamAccess = settings.bitstream; + const itemAccess = settings.item; + + const { file } = this.bulkAccessControlService.createPayloadFile({ + bitstreamAccess, + itemAccess, + state: settings.state + }); + + this.bulkAccessControlService.executeScript( + this.objectsSelected$.value || [], + file + ).pipe(take(1)).subscribe((res) => { + console.log('success', res); + }); + } + + /** + * Generate The RemoteData object containing the list of the selected elements + * @param list + * @private + */ + private generateIdListBySelectedElements(list: SelectableListState): string[] { + return list?.selection?.map((entry: any) => entry.indexableObject.uuid); + } } diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.html b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.html index a5c292eddd..01f36ef03f 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.html +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.html @@ -15,7 +15,7 @@
    -

    bulk-access-settings works!

    +
    diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts index 435f2828b9..306d4eebde 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts @@ -1,25 +1,80 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; import { BulkAccessSettingsComponent } from './bulk-access-settings.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('BulkAccessSettingsComponent', () => { let component: BulkAccessSettingsComponent; let fixture: ComponentFixture; + const mockFormState = { + "bitstream": [], + "item": [ + { + "name": "embargo", + "startDate": { + "year": 2026, + "month": 5, + "day": 31 + }, + "endDate": null + } + ], + "state": { + "item": { + "toggleStatus": true, + "accessMode": "replace" + }, + "bitstream": { + "toggleStatus": false, + "accessMode": "", + "changesLimit": "", + "selectedBitstreams": [] + } + } + }; + + const mockControl: any = jasmine.createSpyObj('AccessControlFormContainerComponent', { + getFormValue: jasmine.createSpy('getFormValue'), + reset: jasmine.createSpy('reset') + }); beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ BulkAccessSettingsComponent ] - }) - .compileComponents(); + imports: [NgbAccordionModule, TranslateModule.forRoot()], + declarations: [BulkAccessSettingsComponent], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(BulkAccessSettingsComponent); component = fixture.componentInstance; fixture.detectChanges(); + component.controlForm = mockControl; }); - it('should create', () => { + it('should create the component', () => { expect(component).toBeTruthy(); }); + + it('should have a method to get the form value', () => { + expect(component.getValue).toBeDefined(); + }); + + it('should have a method to reset the form', () => { + expect(component.reset).toBeDefined(); + }); + + it('should return the correct form value', () => { + const expectedValue = mockFormState; + (component.controlForm as any).getFormValue.and.returnValue(mockFormState) + const actualValue = component.getValue(); + expect(actualValue).toEqual(expectedValue); + }); + + it('should call reset on the control form', () => { + component.reset(); + expect(component.controlForm.reset).toHaveBeenCalled(); + }); }); diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts index cad9e5b981..978fc41ed8 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts @@ -1,20 +1,34 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; +import { + AccessControlFormContainerComponent +} from '../../../shared/access-control-form-container/access-control-form-container.component'; @Component({ selector: 'ds-bulk-access-settings', - templateUrl: './bulk-access-settings.component.html', - styleUrls: ['./bulk-access-settings.component.scss'] + templateUrl: 'bulk-access-settings.component.html', + styleUrls: ['./bulk-access-settings.component.scss'], + exportAs: 'dsBulkSettings' }) -export class BulkAccessSettingsComponent implements OnInit { +export class BulkAccessSettingsComponent { /** - * The selection list id + * The SectionsDirective reference */ - @Input() listId!: string; + @ViewChild('dsAccessControlForm') controlForm: AccessControlFormContainerComponent; - constructor() { } + /** + * Will be used from a parent component to read the value of the form + */ + getValue() { + return this.controlForm.getFormValue(); + } - ngOnInit(): void { + /** + * Reset the form to its initial state + * This will also reset the state of the child components (bitstream and item access) + */ + reset() { + this.controlForm.reset() } } diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.spec.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.spec.ts index 5c97e3abc5..ca715a238c 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.spec.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.spec.ts @@ -11,7 +11,7 @@ import { ControlMaxEndDatePipe } from './control-max-end-date.pipe'; import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; -fdescribe('AccessControlArrayFormComponent', () => { +describe('AccessControlArrayFormComponent', () => { let component: AccessControlArrayFormComponent; let fixture: ComponentFixture; diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 10ce81d6fc..7e46a53ed5 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -65,6 +65,11 @@ export class AccessControlFormContainerComponent impleme * Will be used from a parent component to read the value of the form */ getFormValue() { + console.log({ + bitstream: this.bitstreamAccessCmp.getValue(), + item: this.itemAccessCmp.getValue(), + state: this.state + }); return { bitstream: this.bitstreamAccessCmp.getValue(), item: this.itemAccessCmp.getValue(), diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts index 9e733b5694..1bd2efcfef 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts @@ -1,12 +1,14 @@ import { TestBed } from '@angular/core/testing'; -import { BulkAccessControlService } from './bulk-access-control.service'; +import { BulkAccessControlService, BulkAccessPayload } from './bulk-access-control.service'; import { ScriptDataService } from '../../core/data/processes/script-data.service'; import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; -fdescribe('BulkAccessControlService', () => { +describe('BulkAccessControlService', () => { let service: BulkAccessControlService; let scriptServiceSpy: jasmine.SpyObj; + + beforeEach(() => { const spy = jasmine.createSpyObj('ScriptDataService', ['invoke']); TestBed.configureTestingModule({ @@ -25,7 +27,7 @@ fdescribe('BulkAccessControlService', () => { describe('createPayloadFile', () => { it('should create a file and return the URL and file object', () => { - const payload = { data: 'test' }; + const payload: BulkAccessPayload = { state: null, bitstreamAccess: null, itemAccess: null }; const result = service.createPayloadFile(payload); expect(result.url).toBeTruthy(); diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index f5afe338dd..77fbcbffb2 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -4,11 +4,17 @@ import { ScriptDataService } from '../../core/data/processes/script-data.service import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; import { AccessControlFormState } from './access-control-form-container.component'; +export interface BulkAccessPayload { + state: AccessControlFormState; + bitstreamAccess: any; + itemAccess: any; +} + @Injectable({ providedIn: 'root' }) export class BulkAccessControlService { constructor(private scriptService: ScriptDataService) {} - createPayloadFile(payload: { state: AccessControlFormState, bitstreamAccess, itemAccess }) { + createPayloadFile(payload: BulkAccessPayload) { const content = convertToBulkAccessControlFileModel(payload); const blob = new Blob([JSON.stringify(content, null, 2)], { diff --git a/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts b/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts index 1535671f79..496ef28761 100644 --- a/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts +++ b/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts @@ -11,7 +11,7 @@ import { } from './selectable-list.actions'; import { AppState } from '../../../app.reducer'; -class SelectableObject extends ListableObject { +export class SelectableObject extends ListableObject { constructor(private value: string) { super(); } From f3aa2d47a6dc5b1397a3f683452b90d6fe1f4784 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 11 May 2023 11:10:00 +0200 Subject: [PATCH 21/45] [CST-9636] finalize implementation --- .../browse/bulk-access-browse.component.ts | 5 +- .../bulk-access/bulk-access.component.spec.ts | 97 +++++++++++++------ .../bulk-access/bulk-access.component.ts | 65 +++++++------ 3 files changed, 109 insertions(+), 58 deletions(-) diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts index 53a7302b46..eff5942d4e 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { BehaviorSubject, Subscription } from 'rxjs'; -import { distinctUntilChanged, map, tap } from 'rxjs/operators'; +import { distinctUntilChanged, map } from 'rxjs/operators'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; @@ -67,8 +67,7 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { this.subs.push( this.selectableListService.getSelectableList(this.listId).pipe( distinctUntilChanged(), - map((list: SelectableListState) => this.generatePaginatedListBySelectedElements(list)), - tap(console.log) + map((list: SelectableListState) => this.generatePaginatedListBySelectedElements(list)) ).subscribe(this.objectsSelected$) ) } diff --git a/src/app/access-control/bulk-access/bulk-access.component.spec.ts b/src/app/access-control/bulk-access/bulk-access.component.spec.ts index 3372f98dab..8a64d01df4 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.spec.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.spec.ts @@ -10,6 +10,9 @@ import { SelectableListService } from '../../shared/object-list/selectable-list/ import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { Process } from '../../process-page/processes/process.model'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; fdescribe('BulkAccessComponent', () => { let component: BulkAccessComponent; @@ -47,6 +50,13 @@ fdescribe('BulkAccessComponent', () => { } }; + const mockFile = { + "uuids": [ + '1234', '5678' + ], + "file": { } + } + const mockSettings: any = jasmine.createSpyObj('AccessControlFormContainerComponent', { getValue: jasmine.createSpy('getValue'), reset: jasmine.createSpy('reset') @@ -55,12 +65,18 @@ fdescribe('BulkAccessComponent', () => { const selectableListState: SelectableListState = { id: 'test', selection }; const expectedIdList = ['1234', '5678']; + const selectableListStateEmpty: SelectableListState = { id: 'test', selection: [] }; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ TranslateModule.forRoot() ], + imports: [ + RouterTestingModule, + TranslateModule.forRoot() + ], declarations: [ BulkAccessComponent ], providers: [ { provide: BulkAccessControlService, useValue: bulkAccessControlServiceMock }, + { provide: NotificationsService, useValue: NotificationsServiceStub }, { provide: SelectableListService, useValue: selectableListServiceMock } ], schemas: [NO_ERRORS_SCHEMA] @@ -73,43 +89,70 @@ fdescribe('BulkAccessComponent', () => { component = fixture.componentInstance; bulkAccessControlService = TestBed.inject(BulkAccessControlService); selectableListService = TestBed.inject(SelectableListService); - (component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListState)); - fixture.detectChanges(); - component.settings = mockSettings; + }); afterEach(() => { fixture.destroy(); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('when there are no elements selected', () => { + + beforeEach(() => { + + (component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListStateEmpty)); + fixture.detectChanges(); + component.settings = mockSettings; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should generate the id list by selected elements', () => { + expect(component.objectsSelected$.value).toEqual([]); + }); + + it('should disable the execute button when there are no objects selected', () => { + expect(component.canExport()).toBe(false); + }); + }); - it('should generate the id list by selected elements', () => { - expect(component.objectsSelected$.value).toEqual(expectedIdList); - }); + describe('when there are elements selected', () => { - it('should disable the execute button when there are no objects selected', () => { - expect(component.canExport()).toBe(false); - }); + beforeEach(() => { - it('should enable the execute button when there are objects selected', () => { - component.objectsSelected$.next(['1234']); - expect(component.canExport()).toBe(true); - }); + (component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListState)); + fixture.detectChanges(); + component.settings = mockSettings; + }); - it('should call the settings reset method when reset is called', () => { - spyOn(component.settings, 'reset'); - component.reset(); - expect(component.settings.reset).toHaveBeenCalled(); - }); + it('should create', () => { + expect(component).toBeTruthy(); + }); - it('should call the bulkAccessControlService executeScript method when submit is called', () => { - (component.settings as any).getValue.and.returnValue(mockFormState) - bulkAccessControlService.executeScript.and.returnValue(createSuccessfulRemoteDataObject$(new Process())); - component.objectsSelected$.next(['1234']); - component.submit(); - expect(bulkAccessControlService.executeScript).toHaveBeenCalled(); + it('should generate the id list by selected elements', () => { + expect(component.objectsSelected$.value).toEqual(expectedIdList); + }); + + it('should enable the execute button when there are objects selected', () => { + component.objectsSelected$.next(['1234']); + expect(component.canExport()).toBe(true); + }); + + it('should call the settings reset method when reset is called', () => { + component.reset(); + expect(component.settings.reset).toHaveBeenCalled(); + }); + + it('should call the bulkAccessControlService executeScript method when submit is called', () => { + (component.settings as any).getValue.and.returnValue(mockFormState); + bulkAccessControlService.createPayloadFile.and.returnValue(mockFile); + bulkAccessControlService.executeScript.and.returnValue(createSuccessfulRemoteDataObject$(new Process())); + component.objectsSelected$.next(['1234']); + component.submit(); + expect(bulkAccessControlService.executeScript).toHaveBeenCalled(); + }); }); }); diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts index 8a82edb273..b928f3da4a 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -1,36 +1,28 @@ import { Component, OnInit, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; + +import { BehaviorSubject, Subscription } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.component'; -import { distinctUntilChanged, map, take, tap } from 'rxjs/operators'; import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; -import { BehaviorSubject, Subscription } from 'rxjs'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; +import { Process } from '../../process-page/processes/process.model'; +import { isNotEmpty } from '../../shared/empty.util'; +import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; @Component({ selector: 'ds-bulk-access', - // templateUrl: './bulk-access.component.html', - template: `
    - -
    - - -
    - -
    - - -
    -
    `, + templateUrl: './bulk-access.component.html', styleUrls: ['./bulk-access.component.scss'] }) export class BulkAccessComponent implements OnInit { - /** * The selection list id */ @@ -53,16 +45,18 @@ export class BulkAccessComponent implements OnInit { constructor( private bulkAccessControlService: BulkAccessControlService, - private selectableListService: SelectableListService - ) { } + private notificationsService: NotificationsService, + private router: Router, + private selectableListService: SelectableListService, + private translationService: TranslateService + ) { + } ngOnInit(): void { this.subs.push( this.selectableListService.getSelectableList(this.listId).pipe( distinctUntilChanged(), - tap(console.log), - map((list: SelectableListState) => this.generateIdListBySelectedElements(list)), - tap(console.log) + map((list: SelectableListState) => this.generateIdListBySelectedElements(list)) ).subscribe(this.objectsSelected$) ) } @@ -97,9 +91,24 @@ export class BulkAccessComponent implements OnInit { this.bulkAccessControlService.executeScript( this.objectsSelected$.value || [], file - ).pipe(take(1)).subscribe((res) => { - console.log('success', res); - }); + ).pipe( + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasSucceeded) { + const title = this.translationService.get('process.new.notification.success.title'); + const content = this.translationService.get('process.new.notification.success.content'); + this.notificationsService.success(title, content); + if (isNotEmpty(rd.payload)) { + this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId)); + } + return true; + } else { + const title = this.translationService.get('process.new.notification.error.title'); + const content = this.translationService.get('process.new.notification.error.content'); + this.notificationsService.error(title, content); + return false; + } + })).subscribe(); } /** From a6897e9a6de8e2a1e1b7236453e83294ca03ccdb Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Thu, 11 May 2023 15:01:12 +0200 Subject: [PATCH 22/45] [CST-9639] Validation, translations and style fixes --- .../collection-access-control.component.html | 2 +- .../community-access-control.component.html | 2 +- .../item-access-control.component.html | 2 +- .../access-control-array-form.component.html | 2 +- .../access-control-array-form.component.ts | 7 +++++++ .../access-control-form-container.component.html | 14 +++++++++----- .../access-control-form-container.component.ts | 15 ++++++++++++--- .../bulk-access-control.service.ts | 13 ++++++++----- ...control-select-bitstreams-modal.component.html | 8 +++++--- src/assets/i18n/en.json5 | 9 +++++---- 10 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html index c53bf9c6ac..4e957cf867 100644 --- a/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html +++ b/src/app/collection-page/edit-collection-page/collection-access-control/collection-access-control.component.html @@ -1,7 +1,7 @@ -

    {{'collection-access-control-title' | translate}}

    diff --git a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html index 89999955eb..b4f2123b86 100644 --- a/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html +++ b/src/app/community-page/edit-community-page/community-access-control/community-access-control.component.html @@ -1,6 +1,6 @@ -

    {{'community-access-control-title' | translate }}

    diff --git a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html index 7bef310717..782e24fa2d 100644 --- a/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html +++ b/src/app/item-page/edit-item-page/item-access-control/item-access-control.component.html @@ -1,6 +1,6 @@ -

    {{ 'item-access-control-title' | translate }}

    diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index 9b1f018ffb..71f327423f 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -1,5 +1,5 @@ -
    +
    {{'access-control-no-access-conditions-warning-message' | translate}}
    diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts index 1e978d9aa9..d4cd923128 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts @@ -34,6 +34,13 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { return this.form.get('accessControl') as FormArray; } + get allControlsAreEmpty() { + if (this.accessControl.length === 0) { + return true; + } + return this.accessControl.value.every(x => x.itemName === null || x.itemName === ''); + } + /** * Add a new access control item to the form. * Start and end date are disabled by default. diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.html b/src/app/shared/access-control-form-container/access-control-form-container.component.html index 5e5c8311c4..a5965839c1 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.html +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.html @@ -1,7 +1,11 @@
    -
    +
    - + +
    @@ -75,7 +79,7 @@
    - @@ -85,9 +89,9 @@ diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 7e46a53ed5..f27fb4b5f0 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -16,6 +16,7 @@ import { import { BulkAccessConfigDataService } from '../../core/config/bulk-access-config-data.service'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { BulkAccessConditionOptions } from '../../core/config/models/bulk-access-condition-options.model'; +import { AlertType } from '../alert/aletr-type'; @Component({ selector: 'ds-access-control-form-container', @@ -30,6 +31,11 @@ export class AccessControlFormContainerComponent impleme */ @Input() showLimitToSpecificBitstreams = false; + /** + * The title message of the access control form (translate key) + */ + @Input() titleMessage = ''; + /** * The item to which the access control form applies */ @@ -45,6 +51,8 @@ export class AccessControlFormContainerComponent impleme @ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent; @ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent; + readonly AlertType = AlertType; + constructor( private bulkAccessConfigService: BulkAccessConfigDataService, private bulkAccessControlService: BulkAccessControlService, @@ -145,18 +153,19 @@ export class AccessControlFormContainerComponent impleme ngOnDestroy(): void { this.selectableListService.deselectAll(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID); } + } const initialState: AccessControlFormState = { item: { toggleStatus: false, - accessMode: '', + accessMode: 'replace', }, bitstream: { toggleStatus: false, - accessMode: '', - changesLimit: '', // 'all' | 'selected' + accessMode: 'replace', + changesLimit: 'all', // 'all' | 'selected' selectedBitstreams: [] as ListableObject[], }, }; diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 77fbcbffb2..057a64906f 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -43,22 +43,25 @@ export class BulkAccessControlService { } export const convertToBulkAccessControlFileModel = (payload: { state: AccessControlFormState, bitstreamAccess: AccessCondition[], itemAccess: AccessCondition[] }): BulkAccessControlFileModel => { + const itemEnabled = payload.state.item.toggleStatus; + const bitstreamEnabled = payload.state.bitstream.toggleStatus; + const constraints = { uuid: [] }; - if (payload.state.bitstream.changesLimit === 'selected') { + if (bitstreamEnabled && payload.state.bitstream.changesLimit === 'selected') { // @ts-ignore constraints.uuid = payload.state.bitstream.selectedBitstreams.map((x) => x.id); } return { item: { - mode: payload.state.item.accessMode, - accessConditions: payload.itemAccess + mode: itemEnabled ? payload.state.item.accessMode : '', + accessConditions: itemEnabled ? payload.itemAccess : [] }, bitstream: { constraints, - mode: payload.state.bitstream.accessMode, - accessConditions: payload.bitstreamAccess + mode: bitstreamEnabled ? payload.state.bitstream.accessMode : '', + accessConditions: bitstreamEnabled ? payload.bitstreamAccess : [] } }; }; diff --git a/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html index e4159f3505..8cf0ecea38 100644 --- a/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html +++ b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.html @@ -1,5 +1,7 @@ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 00f2bd46b0..315b0e03fc 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5326,7 +5326,7 @@ "admin.system-wide-alert.title": "System-wide Alerts", - "item-access-control-title": "This form allows you to perform changes to the access condition of all the items owned by collection under this community. Changes can be performed on the access condition for the metadata (item) or for the content (bitstream).", + "item-access-control-title": "This form allows you to perform changes to the access condition of all the item's metadata and all its bitstreams.", "collection-access-control-title": "This form allows you to perform changes to the access condition of all the items owned by collection under this community. Changes can be performed on the access condition for the metadata (item) or for the content (bitstream).", "community-access-control-title": "This form allows you to perform changes to the access condition of all the items owned by collection under this community. Changes can be performed on the access condition for the metadata (item) or for the content (bitstream).", "access-control-item-header-toggle": "Item's Metadata", @@ -5341,8 +5341,9 @@ "access-control-bitstreams-selected": "bitstreams selected", "access-control-reset": "Reset", "access-control-execute": "Execute", - "access-control-add-more": "Add more" - - + "access-control-add-more": "Add more", + "access-control-select-bitstreams-modal.title": "Select bitstreams", + "access-control-select-bitstreams-modal.no-items": "No items to show.", + "access-control-select-bitstreams-modal.close": "Close" } From 377e27b305bcc000ce28972775c9d60c14dd4567 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 12 May 2023 09:25:47 +0200 Subject: [PATCH 23/45] [CST-9636] Fix lint --- .../bulk-access-browse.component.spec.ts | 8 ++-- .../browse/bulk-access-browse.component.ts | 6 +-- .../bulk-access/bulk-access.component.spec.ts | 40 +++++++++---------- .../bulk-access/bulk-access.component.ts | 6 +-- .../bulk-access-settings.component.spec.ts | 36 ++++++++--------- .../bulk-access-settings.component.ts | 2 +- ...ntrol-select-bitstreams-modal.component.ts | 2 +- .../themed-object-list.component.ts | 18 ++++----- src/assets/i18n/en.json5 | 2 +- 9 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts index c8379b06ee..87b2a8d568 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts @@ -69,10 +69,10 @@ describe('BulkAccessBrowseComponent', () => { it('should have an initial remote data with a paginated list as value', () => { const list = buildPaginatedList(new PageInfo({ - "elementsPerPage": 5, - "totalElements": 2, - "totalPages": 1, - "currentPage": 1 + 'elementsPerPage': 5, + 'totalElements': 2, + 'totalPages': 1, + 'currentPage': 1 }), [selected1, selected2]) ; const rd = createSuccessfulRemoteDataObject(list); diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts index eff5942d4e..e806e729c8 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -69,7 +69,7 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { distinctUntilChanged(), map((list: SelectableListState) => this.generatePaginatedListBySelectedElements(list)) ).subscribe(this.objectsSelected$) - ) + ); } pageNext() { @@ -87,7 +87,7 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { private calculatePageCount(pageSize, totalCount = 0) { // we suppose that if we have 0 items we want 1 empty page return totalCount < pageSize ? 1 : Math.ceil(totalCount / pageSize); - }; + } /** * Generate The RemoteData object containing the list of the selected elements @@ -114,6 +114,6 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { this.subs .filter((sub) => hasValue(sub)) .forEach((sub) => sub.unsubscribe()); - this.selectableListService.deselectAll(this.listId) + this.selectableListService.deselectAll(this.listId); } } diff --git a/src/app/access-control/bulk-access/bulk-access.component.spec.ts b/src/app/access-control/bulk-access/bulk-access.component.spec.ts index 8a64d01df4..ccbdabb7a6 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.spec.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.spec.ts @@ -24,38 +24,38 @@ fdescribe('BulkAccessComponent', () => { const bulkAccessControlServiceMock = jasmine.createSpyObj('bulkAccessControlService', ['createPayloadFile', 'executeScript']); const mockFormState = { - "bitstream": [], - "item": [ + 'bitstream': [], + 'item': [ { - "name": "embargo", - "startDate": { - "year": 2026, - "month": 5, - "day": 31 + 'name': 'embargo', + 'startDate': { + 'year': 2026, + 'month': 5, + 'day': 31 }, - "endDate": null + 'endDate': null } ], - "state": { - "item": { - "toggleStatus": true, - "accessMode": "replace" + 'state': { + 'item': { + 'toggleStatus': true, + 'accessMode': 'replace' }, - "bitstream": { - "toggleStatus": false, - "accessMode": "", - "changesLimit": "", - "selectedBitstreams": [] + 'bitstream': { + 'toggleStatus': false, + 'accessMode': '', + 'changesLimit': '', + 'selectedBitstreams': [] } } }; const mockFile = { - "uuids": [ + 'uuids': [ '1234', '5678' ], - "file": { } - } + 'file': { } + }; const mockSettings: any = jasmine.createSpyObj('AccessControlFormContainerComponent', { getValue: jasmine.createSpy('getValue'), diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts index b928f3da4a..074dbc7f55 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -26,7 +26,7 @@ export class BulkAccessComponent implements OnInit { /** * The selection list id */ - listId: string = 'bulk-access-list'; + listId = 'bulk-access-list'; /** * The list of the objects already selected @@ -58,7 +58,7 @@ export class BulkAccessComponent implements OnInit { distinctUntilChanged(), map((list: SelectableListState) => this.generateIdListBySelectedElements(list)) ).subscribe(this.objectsSelected$) - ) + ); } canExport(): boolean { @@ -70,7 +70,7 @@ export class BulkAccessComponent implements OnInit { * This will also reset the state of the child components (bitstream and item access) */ reset(): void { - this.settings.reset() + this.settings.reset(); } /** diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts index 306d4eebde..229f7eeb11 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts @@ -8,28 +8,28 @@ describe('BulkAccessSettingsComponent', () => { let component: BulkAccessSettingsComponent; let fixture: ComponentFixture; const mockFormState = { - "bitstream": [], - "item": [ + 'bitstream': [], + 'item': [ { - "name": "embargo", - "startDate": { - "year": 2026, - "month": 5, - "day": 31 + 'name': 'embargo', + 'startDate': { + 'year': 2026, + 'month': 5, + 'day': 31 }, - "endDate": null + 'endDate': null } ], - "state": { - "item": { - "toggleStatus": true, - "accessMode": "replace" + 'state': { + 'item': { + 'toggleStatus': true, + 'accessMode': 'replace' }, - "bitstream": { - "toggleStatus": false, - "accessMode": "", - "changesLimit": "", - "selectedBitstreams": [] + 'bitstream': { + 'toggleStatus': false, + 'accessMode': '', + 'changesLimit': '', + 'selectedBitstreams': [] } } }; @@ -68,7 +68,7 @@ describe('BulkAccessSettingsComponent', () => { it('should return the correct form value', () => { const expectedValue = mockFormState; - (component.controlForm as any).getFormValue.and.returnValue(mockFormState) + (component.controlForm as any).getFormValue.and.returnValue(mockFormState); const actualValue = component.getValue(); expect(actualValue).toEqual(expectedValue); }); diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts index 978fc41ed8..eecc016245 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts @@ -28,7 +28,7 @@ export class BulkAccessSettingsComponent { * This will also reset the state of the child components (bitstream and item access) */ reset() { - this.controlForm.reset() + this.controlForm.reset(); } } diff --git a/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts index 171b1d59c1..617803a0c4 100644 --- a/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts +++ b/src/app/shared/access-control-form-container/item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component.ts @@ -13,7 +13,7 @@ import { TranslateService } from '@ngx-translate/core'; import { hasValue } from '../../empty.util'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; -export const ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID = 'item-access-control-select-bitstreams' +export const ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID = 'item-access-control-select-bitstreams'; @Component({ selector: 'ds-item-access-control-select-bitstreams-modal', diff --git a/src/app/shared/object-list/themed-object-list.component.ts b/src/app/shared/object-list/themed-object-list.component.ts index 9448ed5939..04a95de1cb 100644 --- a/src/app/shared/object-list/themed-object-list.component.ts +++ b/src/app/shared/object-list/themed-object-list.component.ts @@ -1,13 +1,13 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { ObjectListComponent } from './object-list.component'; import { ThemedComponent } from '../theme-support/themed.component'; -import {PaginationComponentOptions} from '../pagination/pagination-component-options.model'; -import {SortDirection, SortOptions} from '../../core/cache/models/sort-options.model'; -import {CollectionElementLinkType} from '../object-collection/collection-element-link.type'; -import {Context} from '../../core/shared/context.model'; -import {RemoteData} from '../../core/data/remote-data'; -import {PaginatedList} from '../../core/data/paginated-list.model'; -import {ListableObject} from '../object-collection/shared/listable-object.model'; +import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { CollectionElementLinkType } from '../object-collection/collection-element-link.type'; +import { Context } from '../../core/shared/context.model'; +import { RemoteData } from '../../core/data/remote-data'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +import { ListableObject } from '../object-collection/shared/listable-object.model'; /** * Themed wrapper for ObjectListComponent @@ -53,7 +53,7 @@ export class ThemedObjectListComponent extends ThemedComponent Date: Fri, 12 May 2023 15:07:24 +0200 Subject: [PATCH 24/45] [CST-9636] Fix argument to send to the process --- .../bulk-access/bulk-access.component.spec.ts | 2 +- .../bulk-access/bulk-access.component.ts | 32 +----------- .../bulk-access-control.service.spec.ts | 52 ++++++++++++++++--- .../bulk-access-control.service.ts | 46 ++++++++++++++-- 4 files changed, 90 insertions(+), 42 deletions(-) diff --git a/src/app/access-control/bulk-access/bulk-access.component.spec.ts b/src/app/access-control/bulk-access/bulk-access.component.spec.ts index ccbdabb7a6..e9b253147d 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.spec.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.spec.ts @@ -14,7 +14,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; -fdescribe('BulkAccessComponent', () => { +describe('BulkAccessComponent', () => { let component: BulkAccessComponent; let fixture: ComponentFixture; let bulkAccessControlService: any; diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts index 074dbc7f55..04724614cb 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -1,20 +1,12 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { Router } from '@angular/router'; import { BehaviorSubject, Subscription } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; -import { TranslateService } from '@ngx-translate/core'; import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.component'; import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; -import { getFirstCompletedRemoteData } from '../../core/shared/operators'; -import { RemoteData } from '../../core/data/remote-data'; -import { Process } from '../../process-page/processes/process.model'; -import { isNotEmpty } from '../../shared/empty.util'; -import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths'; -import { NotificationsService } from '../../shared/notifications/notifications.service'; @Component({ selector: 'ds-bulk-access', @@ -45,10 +37,7 @@ export class BulkAccessComponent implements OnInit { constructor( private bulkAccessControlService: BulkAccessControlService, - private notificationsService: NotificationsService, - private router: Router, - private selectableListService: SelectableListService, - private translationService: TranslateService + private selectableListService: SelectableListService ) { } @@ -91,24 +80,7 @@ export class BulkAccessComponent implements OnInit { this.bulkAccessControlService.executeScript( this.objectsSelected$.value || [], file - ).pipe( - getFirstCompletedRemoteData(), - map((rd: RemoteData) => { - if (rd.hasSucceeded) { - const title = this.translationService.get('process.new.notification.success.title'); - const content = this.translationService.get('process.new.notification.success.content'); - this.notificationsService.success(title, content); - if (isNotEmpty(rd.payload)) { - this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId)); - } - return true; - } else { - const title = this.translationService.get('process.new.notification.error.title'); - const content = this.translationService.get('process.new.notification.error.content'); - this.notificationsService.error(title, content); - return false; - } - })).subscribe(); + ).subscribe(); } /** diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts index 1bd2efcfef..2dc2bbc4f3 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts @@ -1,20 +1,57 @@ import { TestBed } from '@angular/core/testing'; -import { BulkAccessControlService, BulkAccessPayload } from './bulk-access-control.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; + +import { BulkAccessControlService } from './bulk-access-control.service'; import { ScriptDataService } from '../../core/data/processes/script-data.service'; import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; +import { NotificationsService } from '../notifications/notifications.service'; +import { NotificationsServiceStub } from '../testing/notifications-service.stub'; +import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; +import { Process } from '../../process-page/processes/process.model'; describe('BulkAccessControlService', () => { let service: BulkAccessControlService; let scriptServiceSpy: jasmine.SpyObj; - + const mockPayload: any = { + 'bitstream': [], + 'item': [ + { + 'name': 'embargo', + 'startDate': { + 'year': 2026, + 'month': 5, + 'day': 31 + }, + 'endDate': null + } + ], + 'state': { + 'item': { + 'toggleStatus': true, + 'accessMode': 'replace' + }, + 'bitstream': { + 'toggleStatus': false, + 'accessMode': '', + 'changesLimit': '', + 'selectedBitstreams': [] + } + } + }; beforeEach(() => { const spy = jasmine.createSpyObj('ScriptDataService', ['invoke']); TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + TranslateModule.forRoot() + ], providers: [ BulkAccessControlService, - { provide: ScriptDataService, useValue: spy } + { provide: ScriptDataService, useValue: spy }, + { provide: NotificationsService, useValue: NotificationsServiceStub }, ] }); service = TestBed.inject(BulkAccessControlService); @@ -27,7 +64,7 @@ describe('BulkAccessControlService', () => { describe('createPayloadFile', () => { it('should create a file and return the URL and file object', () => { - const payload: BulkAccessPayload = { state: null, bitstreamAccess: null, itemAccess: null }; + const payload = mockPayload; const result = service.createPayloadFile(payload); expect(result.url).toBeTruthy(); @@ -39,10 +76,13 @@ describe('BulkAccessControlService', () => { it('should invoke the script service with the correct parameters', () => { const uuids = ['123', '456']; const file = new File(['test'], 'data.json', { type: 'application/json' }); - const expectedParams: ProcessParameter[] = [{ name: 'uuid', value: '123,456' }]; + const expectedParams: ProcessParameter[] = [ + { name: '-u', value: '123,456' }, + { name: '-f', value: 'data.json' } + ]; // @ts-ignore - scriptServiceSpy.invoke.and.returnValue(Promise.resolve({})); + scriptServiceSpy.invoke.and.returnValue(createSuccessfulRemoteDataObject$(new Process())); const result = service.executeScript(uuids, file); diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 057a64906f..706bdde367 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -1,8 +1,19 @@ import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; import { ScriptDataService } from '../../core/data/processes/script-data.service'; import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; import { AccessControlFormState } from './access-control-form-container.component'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; +import { Process } from '../../process-page/processes/process.model'; +import { isNotEmpty } from '../empty.util'; +import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths'; +import { NotificationsService } from '../notifications/notifications.service'; export interface BulkAccessPayload { state: AccessControlFormState; @@ -12,7 +23,14 @@ export interface BulkAccessPayload { @Injectable({ providedIn: 'root' }) export class BulkAccessControlService { - constructor(private scriptService: ScriptDataService) {} + constructor( + private notificationsService: NotificationsService, + private router: Router, + private scriptService: ScriptDataService, + private translationService: TranslateService + ) { + + } createPayloadFile(payload: BulkAccessPayload) { const content = convertToBulkAccessControlFileModel(payload); @@ -26,19 +44,37 @@ export class BulkAccessControlService { }); const url = URL.createObjectURL(file); - window.open(url, '_blank'); // remove this later return { url, file }; } - executeScript(uuids: string[], file: File) { + executeScript(uuids: string[], file: File): Observable { console.log('execute', { uuids, file }); const params: ProcessParameter[] = [ - { name: 'uuid', value: uuids.join(',') }, + { name: '-u', value: uuids.join(',') }, + { name: '-f', value: file.name } ]; - return this.scriptService.invoke('bulk-access-control', params, [file]); + return this.scriptService.invoke('bulk-access-control', params, [file]).pipe( + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasSucceeded) { + const title = this.translationService.get('process.new.notification.success.title'); + const content = this.translationService.get('process.new.notification.success.content'); + this.notificationsService.success(title, content); + if (isNotEmpty(rd.payload)) { + this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId)); + } + return true; + } else { + const title = this.translationService.get('process.new.notification.error.title'); + const content = this.translationService.get('process.new.notification.error.content'); + this.notificationsService.error(title, content); + return false; + } + }) + ); } } From 6ff80700f3092885cf044345db03b4bbab2fa669 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 12 May 2023 16:56:59 +0200 Subject: [PATCH 25/45] [CST-9636] Fix multiple uuid argument --- .../bulk-access-control.service.spec.ts | 5 +++-- .../bulk-access-control.service.ts | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts index 2dc2bbc4f3..16d05edeb7 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.spec.ts @@ -77,8 +77,9 @@ describe('BulkAccessControlService', () => { const uuids = ['123', '456']; const file = new File(['test'], 'data.json', { type: 'application/json' }); const expectedParams: ProcessParameter[] = [ - { name: '-u', value: '123,456' }, - { name: '-f', value: 'data.json' } + { name: '-f', value: 'data.json' }, + { name: '-u', value: '123' }, + { name: '-u', value: '456' }, ]; // @ts-ignore diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 706bdde367..6401ed42eb 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -52,9 +52,11 @@ export class BulkAccessControlService { console.log('execute', { uuids, file }); const params: ProcessParameter[] = [ - { name: '-u', value: uuids.join(',') }, { name: '-f', value: file.name } ]; + uuids.forEach((uuid) => { + params.push({ name: '-u', value: uuid }) + }); return this.scriptService.invoke('bulk-access-control', params, [file]).pipe( getFirstCompletedRemoteData(), From 11c844d97357d9dabb64f05c2b6bae815b7e9ee5 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 12 May 2023 17:24:10 +0200 Subject: [PATCH 26/45] [CST-9636] Format date to be sent to the process --- .../access-control-array-form.component.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts index d4cd923128..14f7526cb7 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts @@ -4,6 +4,7 @@ import { FormArray, FormBuilder, FormControl } from '@angular/forms'; import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; import { AccessesConditionOption } from '../../../core/config/models/config-accesses-conditions-options.model'; +import { dateToISOFormat } from '../../date.util'; @Component({ selector: 'ds-access-control-array-form', @@ -70,7 +71,11 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { getValue() { return this.form.value.accessControl .filter(x => x.itemName !== null && x.itemName !== '') - .map(x => ({ name: x.itemName, startDate: x.startDate || null, endDate: x.endDate || null })); + .map(x => ({ + name: x.itemName, + startDate: (x.startDate ? dateToISOFormat(x.startDate) : null), + endDate: (x.endDate ? dateToISOFormat(x.endDate) : null) + })); } /** From 270e003328af17d746a5945808bb42bd9b34a665 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Mon, 15 May 2023 12:27:44 +0200 Subject: [PATCH 27/45] [CST-9639] Disable Execute button if none is enabled and dont send field in payload if not enabled in form --- ...cess-control-form-container.component.html | 4 +- .../bulk-access-control.service.ts | 41 +++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.html b/src/app/shared/access-control-form-container/access-control-form-container.component.html index a5965839c1..9551b5b736 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.html +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.html @@ -142,7 +142,9 @@ -
    diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 6401ed42eb..71e8115d6b 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -81,36 +81,43 @@ export class BulkAccessControlService { } export const convertToBulkAccessControlFileModel = (payload: { state: AccessControlFormState, bitstreamAccess: AccessCondition[], itemAccess: AccessCondition[] }): BulkAccessControlFileModel => { + let finalPayload: BulkAccessControlFileModel = {}; + const itemEnabled = payload.state.item.toggleStatus; const bitstreamEnabled = payload.state.bitstream.toggleStatus; - const constraints = { uuid: [] }; - - if (bitstreamEnabled && payload.state.bitstream.changesLimit === 'selected') { - // @ts-ignore - constraints.uuid = payload.state.bitstream.selectedBitstreams.map((x) => x.id); + if (itemEnabled) { + finalPayload.item = { + mode: payload.state.item.accessMode, + accessConditions: payload.itemAccess + } } - return { - item: { - mode: itemEnabled ? payload.state.item.accessMode : '', - accessConditions: itemEnabled ? payload.itemAccess : [] - }, - bitstream: { - constraints, - mode: bitstreamEnabled ? payload.state.bitstream.accessMode : '', - accessConditions: bitstreamEnabled ? payload.bitstreamAccess : [] + if (bitstreamEnabled) { + const constraints = { uuid: [] }; + + if (bitstreamEnabled && payload.state.bitstream.changesLimit === 'selected') { + // @ts-ignore + constraints.uuid = payload.state.bitstream.selectedBitstreams.map((x) => x.id); } - }; + + finalPayload.bitstream = { + constraints, + mode: payload.state.bitstream.accessMode, + accessConditions: payload.bitstreamAccess + } + } + + return finalPayload; }; export interface BulkAccessControlFileModel { - item: { + item?: { mode: string; accessConditions: AccessCondition[]; }, - bitstream: { + bitstream?: { constraints: { uuid: string[] }; mode: string; accessConditions: AccessCondition[]; From 2fc2897a3644f9f37d2dbd3c413a74442269b392 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Mon, 15 May 2023 13:10:10 +0200 Subject: [PATCH 28/45] [CST-9639] Show warning only when replace is selected --- .../access-control-array-form.component.html | 2 +- .../access-control-array-form.component.ts | 1 + .../access-control-form-container.component.html | 2 ++ .../access-control-form-container.component.ts | 4 ++-- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index 71f327423f..2e6d9c2fc1 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -1,5 +1,5 @@ -
    +
    {{'access-control-no-access-conditions-warning-message' | translate}}
    diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts index 14f7526cb7..519fc0ae3e 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts @@ -14,6 +14,7 @@ import { dateToISOFormat } from '../../date.util'; }) export class AccessControlArrayFormComponent implements OnInit, OnDestroy { @Input() dropdownOptions: AccessesConditionOption[] = []; + @Input() mode!: 'add' | 'replace'; private destroy$ = new Subject(); diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.html b/src/app/shared/access-control-form-container/access-control-form-container.component.html index 9551b5b736..ccb18b9100 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.html +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.html @@ -49,6 +49,7 @@ @@ -130,6 +131,7 @@ diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index f27fb4b5f0..9394b083b1 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -173,11 +173,11 @@ const initialState: AccessControlFormState = { export interface AccessControlFormState { item: { toggleStatus: boolean, - accessMode: string, + accessMode: 'add' | 'replace', }, bitstream: { toggleStatus: boolean, - accessMode: string, + accessMode: 'add' | 'replace', changesLimit: string, selectedBitstreams: ListableObject[], } From b4340e0b911cb03af23f87b8e52a9baf6d9a3d5c Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Mon, 15 May 2023 15:39:39 +0200 Subject: [PATCH 29/45] [CST-9639] Fix lint issues --- .../bulk-access-control.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 71e8115d6b..5302e3aa4a 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -55,7 +55,7 @@ export class BulkAccessControlService { { name: '-f', value: file.name } ]; uuids.forEach((uuid) => { - params.push({ name: '-u', value: uuid }) + params.push({ name: '-u', value: uuid }); }); return this.scriptService.invoke('bulk-access-control', params, [file]).pipe( @@ -90,7 +90,7 @@ export const convertToBulkAccessControlFileModel = (payload: { state: AccessCont finalPayload.item = { mode: payload.state.item.accessMode, accessConditions: payload.itemAccess - } + }; } if (bitstreamEnabled) { @@ -105,7 +105,7 @@ export const convertToBulkAccessControlFileModel = (payload: { state: AccessCont constraints, mode: payload.state.bitstream.accessMode, accessConditions: payload.bitstreamAccess - } + }; } return finalPayload; From 7f450320b6dcbc94eff3980d3a0cc63044791eca Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Tue, 23 May 2023 16:05:38 +0200 Subject: [PATCH 30/45] Fix circular dependency --- .../access-control-array-form.component.ts | 2 +- ...ess-control-form-container-intial-state.ts | 27 ++++++++++++++++ ...access-control-form-container.component.ts | 32 ++----------------- .../bulk-access-control.service.ts | 2 +- 4 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 src/app/shared/access-control-form-container/access-control-form-container-intial-state.ts diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts index 519fc0ae3e..ec9c5c9a41 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts @@ -70,7 +70,7 @@ export class AccessControlArrayFormComponent implements OnInit, OnDestroy { * @return The form value */ getValue() { - return this.form.value.accessControl + return (this.form.value.accessControl as any[]) .filter(x => x.itemName !== null && x.itemName !== '') .map(x => ({ name: x.itemName, diff --git a/src/app/shared/access-control-form-container/access-control-form-container-intial-state.ts b/src/app/shared/access-control-form-container/access-control-form-container-intial-state.ts new file mode 100644 index 0000000000..6e19e04d84 --- /dev/null +++ b/src/app/shared/access-control-form-container/access-control-form-container-intial-state.ts @@ -0,0 +1,27 @@ +import {ListableObject} from '../object-collection/shared/listable-object.model'; + +export const accessControlInitialFormState: AccessControlFormState = { + item: { + toggleStatus: false, + accessMode: 'replace', + }, + bitstream: { + toggleStatus: false, + accessMode: 'replace', + changesLimit: 'all', // 'all' | 'selected' + selectedBitstreams: [] as ListableObject[], + }, +}; + +export interface AccessControlFormState { + item: { + toggleStatus: boolean, + accessMode: 'add' | 'replace', + }, + bitstream: { + toggleStatus: boolean, + accessMode: 'add' | 'replace', + changesLimit: string, + selectedBitstreams: ListableObject[], + } +} diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 9394b083b1..7ebcc73ed8 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -7,7 +7,6 @@ import { BulkAccessControlService } from './bulk-access-control.service'; import { SelectableListService } from '../object-list/selectable-list/selectable-list.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { map, take } from 'rxjs/operators'; -import { ListableObject } from '../object-collection/shared/listable-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID, @@ -17,6 +16,7 @@ import { BulkAccessConfigDataService } from '../../core/config/bulk-access-confi import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { BulkAccessConditionOptions } from '../../core/config/models/bulk-access-condition-options.model'; import { AlertType } from '../alert/aletr-type'; +import { accessControlInitialFormState } from './access-control-form-container-intial-state'; @Component({ selector: 'ds-access-control-form-container', @@ -61,7 +61,7 @@ export class AccessControlFormContainerComponent impleme private cdr: ChangeDetectorRef ) {} - state = initialState; + state = accessControlInitialFormState; dropdownData$: Observable = this.bulkAccessConfigService.findByName('default').pipe( getFirstCompletedRemoteData(), @@ -92,7 +92,7 @@ export class AccessControlFormContainerComponent impleme reset() { this.bitstreamAccessCmp.reset(); this.itemAccessCmp.reset(); - this.state = initialState; + this.state = accessControlInitialFormState; } /** @@ -156,29 +156,3 @@ export class AccessControlFormContainerComponent impleme } - -const initialState: AccessControlFormState = { - item: { - toggleStatus: false, - accessMode: 'replace', - }, - bitstream: { - toggleStatus: false, - accessMode: 'replace', - changesLimit: 'all', // 'all' | 'selected' - selectedBitstreams: [] as ListableObject[], - }, -}; - -export interface AccessControlFormState { - item: { - toggleStatus: boolean, - accessMode: 'add' | 'replace', - }, - bitstream: { - toggleStatus: boolean, - accessMode: 'add' | 'replace', - changesLimit: string, - selectedBitstreams: ListableObject[], - } -} diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 5302e3aa4a..51eba4275d 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -7,7 +7,7 @@ import { TranslateService } from '@ngx-translate/core'; import { ScriptDataService } from '../../core/data/processes/script-data.service'; import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; -import { AccessControlFormState } from './access-control-form-container.component'; +import { AccessControlFormState } from './access-control-form-container-intial-state'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { RemoteData } from '../../core/data/remote-data'; import { Process } from '../../process-page/processes/process.model'; From bd477765c0fbf43936f0acbc69d7c52466463953 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Mon, 29 May 2023 10:44:03 +0200 Subject: [PATCH 31/45] Added labels and hide dates if disabled --- .../access-control-array-form.component.html | 10 ++++++++-- .../access-control-form-container-intial-state.ts | 4 ++-- .../access-control-form-container.component.ts | 8 +++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index 2e6d9c2fc1..faa28a60e8 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -9,6 +9,7 @@ style="display: grid; grid-template-columns: 1fr 1fr 1fr 50px; grid-gap: 5px">
    +
    -
    +
    +
    -
    +
    +
    + -
    + {{'access-control-option-start-date-note' | translate}}
    -
    -
    - -
    +
    +
    + {{'access-control-option-end-date-note' | translate}}
    - +
    + + +
    diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0618596e60..2b8c64ed13 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5358,23 +5358,53 @@ "admin.system-wide-alert.title": "System-wide Alerts", "item-access-control-title": "This form allows you to perform changes to the access condition of all the item's metadata and all its bitstreams.", + "collection-access-control-title": "This form allows you to perform changes to the access condition of all the items owned by collection under this community. Changes can be performed on the access condition for the metadata (item) or for the content (bitstream).", + "community-access-control-title": "This form allows you to perform changes to the access condition of all the items owned by collection under this community. Changes can be performed on the access condition for the metadata (item) or for the content (bitstream).", + "access-control-item-header-toggle": "Item's Metadata", + "access-control-bitstream-header-toggle": "Bitstreams", + "access-control-mode": "Mode", + "access-control-access-conditions": "Access conditions", + "access-control-no-access-conditions-warning-message": "You have not specified any access conditions, the new items access conditions will be inherited from the owning collection.", + "access-control-replace-all": "Replace access conditions", + "access-control-add-to-existing": "Add to existing ones", + "access-control-limit-to-specific": "Limit the changes to specific bitstreams", + "access-control-process-all-bitstreams": "process all the bitstreams in the item", + "access-control-bitstreams-selected": "bitstreams selected", + "access-control-reset": "Reset", + "access-control-execute": "Execute", + "access-control-add-more": "Add more", + "access-control-select-bitstreams-modal.title": "Select bitstreams", + "access-control-select-bitstreams-modal.no-items": "No items to show.", + "access-control-select-bitstreams-modal.close": "Close", + "access-control-option-label": "Access condition type", + + "access-control-option-note": "Select an access condition to apply on the bitstream once the item is deposited", + + "access-control-option-start-date": "Grant access from", + + "access-control-option-start-date-note": "Select the date from which the relate access condition is applied", + + "access-control-option-end-date": "Grant access until", + + "access-control-option-end-date-note": "Select the date until which the relate access condition is applied", + } From c1dcebbd049d420f88a2a0b90cca8ae6f33fd340 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Thu, 1 Jun 2023 15:27:11 +0200 Subject: [PATCH 35/45] Fix: fix validation and translation Show/hide the datepicker based on the value --- .../access-control-array-form.component.html | 119 ++++++++------- .../access-control-array-form.component.scss | 7 + ...ccess-control-array-form.component.spec.ts | 53 ++++--- .../access-control-array-form.component.ts | 137 +++++++----------- .../control-max-end-date.pipe.ts | 26 ---- .../control-max-start-date.pipe.ts | 27 ---- .../access-control-array-form/to-date.pipe.ts | 23 +++ ...cess-control-form-container.component.html | 4 +- .../access-control-form.module.ts | 38 +++-- src/assets/i18n/en.json5 | 22 +-- 10 files changed, 214 insertions(+), 242 deletions(-) delete mode 100644 src/app/shared/access-control-form-container/access-control-array-form/control-max-end-date.pipe.ts delete mode 100644 src/app/shared/access-control-form-container/access-control-array-form/control-max-start-date.pipe.ts create mode 100644 src/app/shared/access-control-form-container/access-control-array-form/to-date.pipe.ts diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index 9460fb4ffe..9158bc3576 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -1,65 +1,87 @@ - -
    + +
    {{'access-control-no-access-conditions-warning-message' | translate}}
    - -
    + +
    - - - {{'access-control-option-note' | translate}} + + {{'access-control-option-note' | translate}} +
    -
    - - -
    - +
    + +
    + +
    + +
    - {{'access-control-option-start-date-note' | translate}} + + {{'access-control-option-start-date-note' | translate}} +
    -
    - - -
    - +
    + +
    + +
    + +
    - {{'access-control-option-end-date-note' | translate}} + + {{'access-control-option-end-date-note' | translate}} +
    @@ -68,18 +90,17 @@
    - -
    diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts index 4e2fd55967..f08534e8f9 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts @@ -1,5 +1,5 @@ import {Component, Input, OnInit, ViewChild} from '@angular/core'; -import {NgForm} from '@angular/forms'; +import {NgForm, NgModelGroup} from '@angular/forms'; import {AccessesConditionOption} from '../../../core/config/models/config-accesses-conditions-options.model'; import {dateToISOFormat} from '../../date.util'; @@ -17,14 +17,13 @@ export class AccessControlArrayFormComponent implements OnInit { @ViewChild('ngForm', {static: true}) ngForm!: NgForm; form: { accessControls: AccessControlItem[] } = { - accessControls: [] + accessControls: [emptyAccessControlItem()] // Start with one empty access control item }; - ngOnInit(): void { - this.addAccessControlItem(); + formDisabled = true; - // Disable the form by default - setTimeout(() => this.disable(), 0); + ngOnInit(): void { + this.disable(); // Disable the form by default } get allControlsAreEmpty() { @@ -32,29 +31,31 @@ export class AccessControlArrayFormComponent implements OnInit { .every(x => x.itemName === null || x.itemName === ''); } + get showWarning() { + return this.mode === 'replace' && this.allControlsAreEmpty && !this.formDisabled; + } + /** * Add a new access control item to the form. * Start and end date are disabled by default. * @param itemName The name of the item to add */ addAccessControlItem(itemName: string = null) { - this.form.accessControls.push({ - itemName, - startDate: null, - hasStartDate: false, - maxStartDate: null, - endDate: null, - hasEndDate: false, - maxEndDate: null, - }); + this.form.accessControls = [ + ...this.form.accessControls, + {...emptyAccessControlItem(), itemName} + ]; } /** * Remove an access control item from the form. + * @param ngModelGroup * @param index */ - removeAccessControlItem(index: number) { - this.form.accessControls.splice(index, 1); + removeAccessControlItem(ngModelGroup: NgModelGroup, id: number) { + this.ngForm.removeFormGroup(ngModelGroup); + + this.form.accessControls = this.form.accessControls.filter(item => item.id !== id); } /** @@ -77,19 +78,30 @@ export class AccessControlArrayFormComponent implements OnInit { */ reset() { this.form.accessControls = []; + + // Add an empty access control item by default + this.addAccessControlItem(); + + this.disable(); } /** * Disable the form. * This will be used to disable the form from the parent component. */ - disable = () => this.ngForm.control.disable(); + disable = () => { + this.ngForm.form.disable(); + this.formDisabled = true; + }; /** * Enable the form. * This will be used to enable the form from the parent component. */ - enable = () => this.ngForm.control.enable(); + enable = () => { + this.ngForm.form.enable(); + this.formDisabled = false; + }; accessControlChanged(control: AccessControlItem, selectedItem: string) { const item = this.dropdownOptions @@ -105,9 +117,16 @@ export class AccessControlArrayFormComponent implements OnInit { control.maxEndDate = item?.maxEndDate || null; } + trackById(index: number, item: AccessControlItem) { + return item.id; + } + } + export interface AccessControlItem { + id: number; // will be used only locally + itemName: string | null; hasStartDate?: boolean; @@ -118,3 +137,16 @@ export interface AccessControlItem { endDate: string | null; maxEndDate?: string | null; } + +const emptyAccessControlItem = (): AccessControlItem => ({ + id: randomID(), + itemName: null, + startDate: null, + hasStartDate: false, + maxStartDate: null, + endDate: null, + hasEndDate: false, + maxEndDate: null, +}); + +const randomID = () => Math.floor(Math.random() * 1000000); diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.html b/src/app/shared/access-control-form-container/access-control-form-container.component.html index afee07139f..a5173d10d7 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.html +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.html @@ -153,12 +153,12 @@
    -
    diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index 51eba4275d..6fb6b62532 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -21,6 +21,9 @@ export interface BulkAccessPayload { itemAccess: any; } +/** + * This service is used to create a payload file and execute the bulk access control script + */ @Injectable({ providedIn: 'root' }) export class BulkAccessControlService { constructor( @@ -28,10 +31,13 @@ export class BulkAccessControlService { private router: Router, private scriptService: ScriptDataService, private translationService: TranslateService - ) { - - } + ) {} + /** + * Create a payload file from the given payload and return the file and the url to the file + * The created file will be used as input for the bulk access control script + * @param payload The payload to create the file from + */ createPayloadFile(payload: BulkAccessPayload) { const content = convertToBulkAccessControlFileModel(payload); @@ -48,6 +54,11 @@ export class BulkAccessControlService { return { url, file }; } + /** + * Execute the bulk access control script with the given uuids and file + * @param uuids + * @param file + */ executeScript(uuids: string[], file: File): Observable { console.log('execute', { uuids, file }); @@ -80,6 +91,10 @@ export class BulkAccessControlService { } } +/** + * Convert the given payload to a BulkAccessControlFileModel + * @param payload + */ export const convertToBulkAccessControlFileModel = (payload: { state: AccessControlFormState, bitstreamAccess: AccessCondition[], itemAccess: AccessCondition[] }): BulkAccessControlFileModel => { let finalPayload: BulkAccessControlFileModel = {}; diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 223c2fce27..a454131175 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -225,7 +225,9 @@ "admin.registries.schema.title": "Metadata Schema Registry", + "admin.access-control.bulk-access.breadcrumbs": "Bulk Access Management", + "administrativeBulkAccess.search.results.head": "Search Results", "admin.access-control.bulk-access": "Bulk Access Management", @@ -5399,14 +5401,14 @@ "access-control-option-label": "Access condition type", - "access-control-option-note": "Select an access condition to apply on the bitstream once the item is deposited", + "access-control-option-note": "Choose an access condition to apply to selected objects.", "access-control-option-start-date": "Grant access from", - "access-control-option-start-date-note": "Select the date from which the relate access condition is applied", + "access-control-option-start-date-note": "Select the date from which the related access condition is applied", "access-control-option-end-date": "Grant access until", - "access-control-option-end-date-note": "Select the date until which the relate access condition is applied", + "access-control-option-end-date-note": "Select the date until which the related access condition is applied", } From 12b3eb839ba63032d715bb4c25ac1a2ce00f492e Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 7 Jun 2023 17:29:05 +0200 Subject: [PATCH 41/45] CST-9639: Added and fixed unit tests --- .../access-control-array-form.component.html | 4 +- ...ccess-control-array-form.component.spec.ts | 32 ++- .../access-control-array-form.component.ts | 6 +- ...s-control-form-container.component.spec.ts | 187 ++++++++++++++---- ...access-control-form-container.component.ts | 2 +- 5 files changed, 178 insertions(+), 53 deletions(-) diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index 963ae165e7..efe2259328 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -4,7 +4,7 @@
    -
    +
    @@ -91,7 +91,7 @@
    diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.spec.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.spec.ts index 88d462ad79..964eb30de2 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.spec.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.spec.ts @@ -45,12 +45,14 @@ describe('AccessControlArrayFormComponent', () => { }); it('should remove access control item', () => { - component.removeAccessControlItem(0); - expect(component.form.accessControls.length).toEqual(0); + expect(component.form.accessControls.length).toEqual(1); component.addAccessControlItem(); - component.removeAccessControlItem(0); - expect(component.form.accessControls.length).toEqual(0); + expect(component.form.accessControls.length).toEqual(2); + + const id = component.form.accessControls[0].id; + component.removeAccessControlItem(id); + expect(component.form.accessControls.length).toEqual(1); }); it('should reset form value', () => { @@ -69,7 +71,12 @@ describe('AccessControlArrayFormComponent', () => { it('should display a select dropdown with options', () => { - const selectElement: DebugElement = fixture.debugElement.query(By.css('select#accesscontroloption-0')); + component.enable(); + fixture.detectChanges(); + + const id = component.form.accessControls[0].id; + + const selectElement: DebugElement = fixture.debugElement.query(By.css(`select#accesscontroloption-${id}`)); expect(selectElement).toBeTruthy(); const options = selectElement.nativeElement.querySelectorAll('option'); @@ -81,6 +88,9 @@ describe('AccessControlArrayFormComponent', () => { }); it('should add new access control items when clicking "Add more" button', () => { + component.enable(); + fixture.detectChanges(); + const addButton: DebugElement = fixture.debugElement.query(By.css(`button#add-btn-${component.type}`)); addButton.nativeElement.click(); fixture.detectChanges(); @@ -90,11 +100,17 @@ describe('AccessControlArrayFormComponent', () => { }); it('should remove access control items when clicking remove button', () => { - const removeButton: DebugElement = fixture.debugElement.query(By.css('button.btn-outline-danger')); - removeButton.nativeElement.click(); + component.enable(); + + component.addAccessControlItem('test'); + + fixture.detectChanges(); + + const removeButton: DebugElement[] = fixture.debugElement.queryAll(By.css('button.btn-outline-danger')); + removeButton[1].nativeElement.click(); fixture.detectChanges(); const accessControlItems = fixture.debugElement.queryAll(By.css('.access-control-item')); - expect(accessControlItems.length).toEqual(0); + expect(accessControlItems.length).toEqual(1); }); }); diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts index f08534e8f9..227de596ff 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.ts @@ -1,5 +1,5 @@ import {Component, Input, OnInit, ViewChild} from '@angular/core'; -import {NgForm, NgModelGroup} from '@angular/forms'; +import {NgForm} from '@angular/forms'; import {AccessesConditionOption} from '../../../core/config/models/config-accesses-conditions-options.model'; import {dateToISOFormat} from '../../date.util'; @@ -52,9 +52,7 @@ export class AccessControlArrayFormComponent implements OnInit { * @param ngModelGroup * @param index */ - removeAccessControlItem(ngModelGroup: NgModelGroup, id: number) { - this.ngForm.removeFormGroup(ngModelGroup); - + removeAccessControlItem(id: number) { this.form.accessControls = this.form.accessControls.filter(item => item.id !== id); } diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts index 66ef20444e..4d02f7a52d 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.spec.ts @@ -1,38 +1,149 @@ -// -// describe('AccessControlFormContainerComponent', () => { -// let component: AccessControlFormContainerComponent; -// let fixture: ComponentFixture>; -// -// let bulkAccessConfigDataServiceMock: BulkAccessConfigDataService; -// -// beforeEach(async () => { -// -// bulkAccessConfigDataServiceMock = jasmine.createSpyObj('BulkAccessConfigDataService', { -// findByName: jasmine.createSpy('findByName'), -// }); -// -// -// await TestBed.configureTestingModule({ -// declarations: [ AccessControlFormContainerComponent ], -// imports: [ CommonModule, ReactiveFormsModule, SharedBrowseByModule, TranslateModule, NgbDatepickerModule ], -// providers: [ -// { provide: BulkAccessConfigDataService, useValue: bulkAccessConfigDataServiceMock }, -// // private bulkAccessControlService: BulkAccessControlService, -// // private selectableListService: SelectableListService, -// // protected modalService: NgbModal, -// // private cdr: ChangeDetectorRef -// ] -// }) -// .compileComponents(); -// }); -// -// beforeEach(() => { -// fixture = TestBed.createComponent(AccessControlFormContainerComponent); -// component = fixture.componentInstance; -// fixture.detectChanges(); -// }); -// -// it('should create', () => { -// expect(component).toBeTruthy(); -// }); -// }); +import {ComponentFixture, fakeAsync, TestBed} from '@angular/core/testing'; +import {NgbDatepickerModule, NgbModal, NgbModalRef} from '@ng-bootstrap/ng-bootstrap'; +import {Component} from '@angular/core'; +import {of} from 'rxjs'; +import {AccessControlFormContainerComponent} from './access-control-form-container.component'; +import {BulkAccessControlService} from './bulk-access-control.service'; +import {BulkAccessConfigDataService} from '../../core/config/bulk-access-config-data.service'; +import {Item} from '../../core/shared/item.model'; +import {SelectableListService} from '../object-list/selectable-list/selectable-list.service'; +import {createAccessControlInitialFormState} from './access-control-form-container-intial-state'; +import {CommonModule} from '@angular/common'; +import {SharedBrowseByModule} from '../browse-by/shared-browse-by.module'; +import {TranslateModule} from '@ngx-translate/core'; +import {FormsModule} from '@angular/forms'; +import {UiSwitchModule} from 'ngx-ui-switch'; +import { + ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID +} from './item-access-control-select-bitstreams-modal/item-access-control-select-bitstreams-modal.component'; +import {AccessControlFormModule} from './access-control-form.module'; + + +describe('AccessControlFormContainerComponent', () => { + let component: AccessControlFormContainerComponent; + let fixture: ComponentFixture>; + + +// Mock NgbModal + @Component({selector: 'ds-ngb-modal', template: ''}) + class MockNgbModalComponent { + } + +// Mock dependencies + const mockBulkAccessControlService = { + createPayloadFile: jasmine.createSpy('createPayloadFile').and.returnValue({file: 'mocked-file'}), + executeScript: jasmine.createSpy('executeScript').and.returnValue(of('success')), + }; + + const mockBulkAccessConfigDataService = { + findByName: jasmine.createSpy('findByName').and.returnValue(of({payload: {options: []}})), + }; + + const mockSelectableListService = { + getSelectableList: jasmine.createSpy('getSelectableList').and.returnValue(of({selection: []})), + deselectAll: jasmine.createSpy('deselectAll'), + }; + + const mockNgbModal = { + open: jasmine.createSpy('open').and.returnValue( + { componentInstance: {}, closed: of({})} as NgbModalRef + ) + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AccessControlFormContainerComponent, MockNgbModalComponent], + imports: [ + CommonModule, + FormsModule, + SharedBrowseByModule, + AccessControlFormModule, + TranslateModule.forRoot(), + NgbDatepickerModule, + UiSwitchModule + ], + providers: [ + {provide: BulkAccessControlService, useValue: mockBulkAccessControlService}, + {provide: BulkAccessConfigDataService, useValue: mockBulkAccessConfigDataService}, + {provide: SelectableListService, useValue: mockSelectableListService}, + {provide: NgbModal, useValue: mockNgbModal}, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AccessControlFormContainerComponent); + component = fixture.componentInstance; + component.state = createAccessControlInitialFormState(); + fixture.detectChanges(); + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should reset the form', fakeAsync(() => { + fixture.detectChanges(); + const resetSpy = spyOn(component.bitstreamAccessCmp, 'reset'); + spyOn(component.itemAccessCmp, 'reset'); + + component.reset(); + + expect(resetSpy).toHaveBeenCalled(); + expect(component.itemAccessCmp.reset).toHaveBeenCalled(); + expect(component.state).toEqual(createAccessControlInitialFormState()); + })); + + it('should submit the form', () => { + const bitstreamAccess = 'bitstreamAccess'; + const itemAccess = 'itemAccess'; + component.bitstreamAccessCmp.getValue = jasmine.createSpy('getValue').and.returnValue(bitstreamAccess); + component.itemAccessCmp.getValue = jasmine.createSpy('getValue').and.returnValue(itemAccess); + component.itemRD = {payload: {uuid: 'item-uuid'}} as any; + + component.submit(); + + expect(mockBulkAccessControlService.createPayloadFile).toHaveBeenCalledWith({ + bitstreamAccess, + itemAccess, + state: createAccessControlInitialFormState(), + }); + expect(mockBulkAccessControlService.executeScript).toHaveBeenCalledWith(['item-uuid'], 'mocked-file'); + }); + + it('should handle the status change for bitstream access', () => { + component.bitstreamAccessCmp.enable = jasmine.createSpy('enable'); + component.bitstreamAccessCmp.disable = jasmine.createSpy('disable'); + + component.handleStatusChange('bitstream', true); + expect(component.bitstreamAccessCmp.enable).toHaveBeenCalled(); + + component.handleStatusChange('bitstream', false); + expect(component.bitstreamAccessCmp.disable).toHaveBeenCalled(); + }); + + it('should handle the status change for item access', () => { + component.itemAccessCmp.enable = jasmine.createSpy('enable'); + component.itemAccessCmp.disable = jasmine.createSpy('disable'); + + component.handleStatusChange('item', true); + expect(component.itemAccessCmp.enable).toHaveBeenCalled(); + + component.handleStatusChange('item', false); + expect(component.itemAccessCmp.disable).toHaveBeenCalled(); + }); + + it('should open the select bitstreams modal', () => { + const modalService = TestBed.inject(NgbModal); + + component.openSelectBitstreamsModal(new Item()); + expect(modalService.open).toHaveBeenCalled(); + }); + + it('should unsubscribe and deselect all on component destroy', () => { + component.ngOnDestroy(); + expect(component.selectableListService.deselectAll).toHaveBeenCalledWith( + ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID + ); + }); +}); diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index a97859a599..b13943e07a 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -58,7 +58,7 @@ export class AccessControlFormContainerComponent impleme constructor( private bulkAccessConfigService: BulkAccessConfigDataService, private bulkAccessControlService: BulkAccessControlService, - private selectableListService: SelectableListService, + public selectableListService: SelectableListService, protected modalService: NgbModal, private cdr: ChangeDetectorRef ) {} From b44acd68eea20ce3876edecf6962e342dfe63b3e Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Thu, 8 Jun 2023 09:52:25 +0200 Subject: [PATCH 42/45] CST-9639: Fix maxStartDate issue --- .../access-control-array-form.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index efe2259328..117cc8d388 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -39,7 +39,7 @@ placeholder="yyyy-mm-dd" [(ngModel)]="control.startDate" name="startDate-{{control.id}}" - [minDate]="control.maxStartDate | toDate" + [maxDate]="control.maxStartDate | toDate" ngbDatepicker #d="ngbDatepicker" /> From 16ff75c92e60e1073f1a9d846e97d23044a73a07 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Thu, 8 Jun 2023 10:23:33 +0200 Subject: [PATCH 43/45] CST-9639: Fix calendar placement --- .../access-control-array-form.component.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index 117cc8d388..cd56904bd7 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -41,6 +41,7 @@ name="startDate-{{control.id}}" [maxDate]="control.maxStartDate | toDate" ngbDatepicker + placement="top-start top-end bottom-start bottom-end" #d="ngbDatepicker" />
    @@ -69,6 +70,7 @@ name="endDate-{{control.id}}" [maxDate]="control.maxEndDate | toDate" ngbDatepicker + placement="top-start top-end bottom-start bottom-end" #d1="ngbDatepicker" />
    From 84f4f017fbb5068292dc68eb6f2efa4c5bdc33f3 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Thu, 8 Jun 2023 14:52:29 +0200 Subject: [PATCH 44/45] CST-9639: Fix error when modal closes --- .../access-control-form-container.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index b13943e07a..69a598f7ce 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -147,7 +147,7 @@ export class AccessControlFormContainerComponent impleme concatMap(() => this.selectableListService.getSelectableList(ITEM_ACCESS_CONTROL_SELECT_BITSTREAMS_LIST_ID)), take(1) ).subscribe((list) => { - this.state.bitstream.selectedBitstreams = list.selection; + this.state.bitstream.selectedBitstreams = list?.selection || []; this.cdr.detectChanges(); }); } From d4491083954ab4419a93e8a04318c58ac4f2c09f Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 8 Jun 2023 15:23:02 +0200 Subject: [PATCH 45/45] [CST-9636] revert changes to object-list.component --- .../browse/bulk-access-browse.component.html | 34 +++++++---- .../object-list/object-list.component.html | 58 ++++++------------- .../object-list/object-list.component.ts | 5 -- .../themed-object-list.component.ts | 6 -- 4 files changed, 41 insertions(+), 62 deletions(-) diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html index 6ef45cdd5b..c716aedb8b 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.html @@ -34,16 +34,30 @@ {{'admin.access-control.bulk-access-browse.selected.header' | translate: {number: ((objectsSelected$ | async)?.payload?.totalElements) ? (objectsSelected$ | async)?.payload?.totalElements : '0'} }} - + +
      +
    • + + +
    • +
    +
    diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 541a4794b0..b8712b85c5 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -16,46 +16,22 @@ (prev)="goPrev()" (next)="goNext()">
      - -
    • - - - -
    • -
      - -
    • - - - -
    • -
      +
    • + + + +
    diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index 72bff54f59..5161b75459 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -76,11 +76,6 @@ export class ObjectListComponent { */ @Input() importConfig: { buttonLabel: string }; - /** - * If true the object list provided needs to be paginated using the `paginate` pipe - */ - @Input() listToPaginate = false; - /** * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination */ diff --git a/src/app/shared/object-list/themed-object-list.component.ts b/src/app/shared/object-list/themed-object-list.component.ts index 04a95de1cb..14ddf474ad 100644 --- a/src/app/shared/object-list/themed-object-list.component.ts +++ b/src/app/shared/object-list/themed-object-list.component.ts @@ -44,11 +44,6 @@ export class ThemedObjectListComponent extends ThemedComponent