[CST-9636] Added unit tests and the logic to create file object from payload

This commit is contained in:
Enea Jahollari
2023-04-28 16:47:07 +02:00
parent 496853671e
commit 3647292b8f
18 changed files with 938 additions and 34 deletions

View File

@@ -1,3 +1,92 @@
<div class="container">
Access control page!!!
<div class="card">
<div class="card-body">
<p>
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).
</p>
<div class="row mt-5">
<div class="col-12 col-md-6 border-right">
<div class="d-flex align-items-center">
<h4 class="mb-0 mr-4">Item's Metadata</h4>
<ui-switch [(ngModel)]="state.item.toggleStatus"></ui-switch>
</div>
<div class="row mt-3">
<div class="col-12 col-md-3">Mode</div>
<div class="col-12 col-md-8">
<div class="form-check">
<input class="form-check-input" type="radio"
name="itemMode" id="itemReplace" value="replace" [(ngModel)]="state.item.accessMode">
<label class="form-check-label" for="itemReplace">Replace access conditions</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio"
name="itemMode" id="itemAdd" value="add" [(ngModel)]="state.item.accessMode">
<label class="form-check-label" for="itemAdd">Add to existing ones</label>
</div>
</div>
</div>
<div class="mt-3">
<h5>Access conditions</h5>
<div class="alert alert-warning">
You have not specified any access conditions, the new items access conditions will be inherited from the owning collection.
</div>
</div>
<ds-access-control-array-form
#itemAccessCmp
[dropdownOptions]="(dropdownData$ | async)?.itemAccessConditionOptions || []">
</ds-access-control-array-form>
</div>
<div class="col-12 col-md-6">
<div class="d-flex align-items-center">
<h4 class="mb-0 mr-4">Bitstreams</h4>
<ui-switch [(ngModel)]="state.bitstream.toggleStatus"></ui-switch>
</div>
<div class="row mt-3">
<div class="col-12 col-md-3">Mode</div>
<div class="col-12 col-md-8">
<div class="form-check">
<input class="form-check-input" type="radio"
name="bitstreamMode" id="bitstreamReplace" value="replace" [(ngModel)]="state.bitstream.accessMode">
<label class="form-check-label" for="bitstreamReplace">Replace access conditions</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio"
name="bitstreamMode" id="bitstreamAdd" value="add" [(ngModel)]="state.bitstream.accessMode">
<label class="form-check-label" for="bitstreamAdd">Add to existing ones</label>
</div>
</div>
</div>
<div class="mt-3">
<h5>Access conditions</h5>
<div class="alert alert-warning">
You have not specified any access conditions, the new items access conditions will be inherited from the owning collection.
</div>
</div>
<ds-access-control-array-form
#bitstreamAccessCmp
[dropdownOptions]="(dropdownData$ | async)?.bitstreamAccessConditionOptions || []">
</ds-access-control-array-form>
</div>
</div>
<hr>
<div class="d-flex justify-content-end">
<button class="btn btn-lg btn-outline-primary mr-3" (click)="reset()">Reset</button>
<button class="btn btn-lg btn-primary" (click)="submit()">Execute</button>
</div>
</div>
</div>
</div>

View File

@@ -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<TDomain extends DSpaceObject> implements OnInit {
export class CollectionAccessControlComponent implements OnInit {
/**
* The initial DSO object
*/
public dsoRD$: Observable<RemoteData<TDomain>>;
@ViewChild('bitstreamAccessCmp', { static: true }) bitstreamAccessCmp: AccessControlArrayFormComponent;
@ViewChild('itemAccessCmp', { static: true }) itemAccessCmp: AccessControlArrayFormComponent;
/**
* Initialize instance variables
*
* @param {ActivatedRoute} route
*/
constructor(
private route: ActivatedRoute
) {
}
constructor(private collectionAccessControlService: CollectionAccessControlService) {}
state = initialState;
dropdownData$ = this.collectionAccessControlService.dropdownData$.pipe(
shareReplay(1)
);
/**
* 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: '',
},
};

View File

@@ -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<AccessControlDropdownDataResponse> = 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'
}
]
};

View File

@@ -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,

View File

@@ -1 +1,92 @@
<p>community-access-control works!</p>
<div class="container">
<div class="card">
<div class="card-body">
<p>
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).
</p>
<div class="row mt-5">
<div class="col-12 col-md-6 border-right">
<div class="d-flex align-items-center">
<h4 class="mb-0 mr-4">Item's Metadata</h4>
<ui-switch [(ngModel)]="state.item.toggleStatus"></ui-switch>
</div>
<div class="row mt-3">
<div class="col-12 col-md-3">Mode</div>
<div class="col-12 col-md-8">
<div class="form-check">
<input class="form-check-input" type="radio"
name="itemMode" id="itemReplace" value="replace" [(ngModel)]="state.item.accessMode">
<label class="form-check-label" for="itemReplace">Replace access conditions</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio"
name="itemMode" id="itemAdd" value="add" [(ngModel)]="state.item.accessMode">
<label class="form-check-label" for="itemAdd">Add to existing ones</label>
</div>
</div>
</div>
<div class="mt-3">
<h5>Access conditions</h5>
<div class="alert alert-warning">
You have not specified any access conditions, the new items access conditions will be inherited from the owning collection.
</div>
</div>
<ds-access-control-array-form
#itemAccessCmp
[dropdownOptions]="(dropdownData$ | async)?.itemAccessConditionOptions || []">
</ds-access-control-array-form>
</div>
<div class="col-12 col-md-6">
<div class="d-flex align-items-center">
<h4 class="mb-0 mr-4">Bitstreams</h4>
<ui-switch [(ngModel)]="state.bitstream.toggleStatus"></ui-switch>
</div>
<div class="row mt-3">
<div class="col-12 col-md-3">Mode</div>
<div class="col-12 col-md-8">
<div class="form-check">
<input class="form-check-input" type="radio"
name="bitstreamMode" id="bitstreamReplace" value="replace" [(ngModel)]="state.bitstream.accessMode">
<label class="form-check-label" for="bitstreamReplace">Replace access conditions</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio"
name="bitstreamMode" id="bitstreamAdd" value="add" [(ngModel)]="state.bitstream.accessMode">
<label class="form-check-label" for="bitstreamAdd">Add to existing ones</label>
</div>
</div>
</div>
<div class="mt-3">
<h5>Access conditions</h5>
<div class="alert alert-warning">
You have not specified any access conditions, the new items access conditions will be inherited from the owning collection.
</div>
</div>
<ds-access-control-array-form
#bitstreamAccessCmp
[dropdownOptions]="(dropdownData$ | async)?.bitstreamAccessConditionOptions || []">
</ds-access-control-array-form>
</div>
</div>
<hr>
<div class="d-flex justify-content-end">
<button class="btn btn-lg btn-outline-primary mr-3" (click)="reset()">Reset</button>
<button class="btn btn-lg btn-primary" (click)="submit()">Execute</button>
</div>
</div>
</div>
</div>

View File

@@ -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: '',
},
};

View File

@@ -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<AccessControlDropdownDataResponse> = 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'
}
]
};

View File

@@ -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,

View File

@@ -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,

View File

@@ -1 +1,110 @@
<p>item-access-control works!</p>
<div class="container">
<div class="card">
<div class="card-body">
<p>
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).
</p>
<div class="row mt-5">
<div class="col-12 col-md-6 border-right">
<div class="d-flex align-items-center">
<h4 class="mb-0 mr-4">Item's Metadata</h4>
<ui-switch [(ngModel)]="state.item.toggleStatus"></ui-switch>
</div>
<div class="row mt-3">
<div class="col-12 col-md-3">Mode</div>
<div class="col-12 col-md-8">
<div class="form-check">
<input class="form-check-input" type="radio"
name="itemMode" id="itemReplace" value="replace" [(ngModel)]="state.item.accessMode">
<label class="form-check-label" for="itemReplace">Replace access conditions</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio"
name="itemMode" id="itemAdd" value="add" [(ngModel)]="state.item.accessMode">
<label class="form-check-label" for="itemAdd">Add to existing ones</label>
</div>
</div>
</div>
<div class="mt-3">
<h5>Access conditions</h5>
<div class="alert alert-warning">
You have not specified any access conditions, the new items access conditions will be inherited from the owning collection.
</div>
</div>
<ds-access-control-array-form
#itemAccessCmp
[dropdownOptions]="(dropdownData$ | async)?.itemAccessConditionOptions || []">
</ds-access-control-array-form>
</div>
<div class="col-12 col-md-6">
<div class="d-flex align-items-center">
<h4 class="mb-0 mr-4">Bitstreams</h4>
<ui-switch [(ngModel)]="state.bitstream.toggleStatus"></ui-switch>
</div>
<div class="row mt-3">
<div class="col-12">Limit the changes to specific bitstreams</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="radio"
name="changesLimit" id="processAll" value="all" [(ngModel)]="state.bitstream.changesLimit">
<label class="form-check-label" for="processAll">process all the bitstreams in the item</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio"
name="changesLimit" id="processSelected" value="selected" [(ngModel)]="state.bitstream.changesLimit">
<label class="form-check-label" for="processSelected">
{{ state.bitstream.selectedBitstreams.length }} bitstreams selected
</label>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12 col-md-3">Mode</div>
<div class="col-12 col-md-8">
<div class="form-check">
<input class="form-check-input" type="radio"
name="bitstreamMode" id="bitstreamReplace" value="replace" [(ngModel)]="state.bitstream.accessMode">
<label class="form-check-label" for="bitstreamReplace">Replace access conditions</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio"
name="bitstreamMode" id="bitstreamAdd" value="add" [(ngModel)]="state.bitstream.accessMode">
<label class="form-check-label" for="bitstreamAdd">Add to existing ones</label>
</div>
</div>
</div>
<div class="mt-3">
<h5>Access conditions</h5>
<div class="alert alert-warning">
You have not specified any access conditions, the new items access conditions will be inherited from the owning collection.
</div>
</div>
<ds-access-control-array-form
#bitstreamAccessCmp
[dropdownOptions]="(dropdownData$ | async)?.bitstreamAccessConditionOptions || []">
</ds-access-control-array-form>
</div>
</div>
<hr>
<div class="d-flex justify-content-end">
<button class="btn btn-lg btn-outline-primary mr-3" (click)="reset()">Reset</button>
<button class="btn btn-lg btn-primary" (click)="submit()">Execute</button>
</div>
</div>
</div>
</div>

View File

@@ -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: []
},
};

View File

@@ -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<AccessControlDropdownDataResponse> = 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'
}
]
};

View File

@@ -0,0 +1,68 @@
<form [formGroup]="form">
<ng-container *ngFor="let control of accessControl.controls; let i = index">
<div [formGroup]="$any(control)"
class="mt-3" data-testId="access-control-item"
style="display: grid; grid-template-columns: 1fr 1fr 1fr 50px; grid-gap: 5px">
<div>
<select id="accesscontroloption" formControlName="itemName" class="form-control">
<option value=""></option>
<option *ngFor="let option of dropdownOptions" [value]="option.name">
{{ option.name }}
</option>
</select>
</div>
<div>
<div class="input-group">
<input
class="form-control"
placeholder="yyyy-mm-dd"
name="dp"
formControlName="startDate"
[minDate]="control | maxStartDate: dropdownOptions"
ngbDatepicker
#d="ngbDatepicker"
/>
<div class="input-group-append">
<button class="btn btn-outline-secondary fas fa-calendar" (click)="d.toggle()"
type="button"></button>
</div>
</div>
</div>
<div>
<div class="input-group">
<input
class="form-control"
placeholder="yyyy-mm-dd"
name="dp"
formControlName="endDate"
[maxDate]="control | maxEndDate: dropdownOptions"
ngbDatepicker
#d1="ngbDatepicker"
/>
<div class="input-group-append">
<button class="btn btn-outline-secondary fas fa-calendar" (click)="d1.toggle()"
type="button">
</button>
</div>
</div>
</div>
<div>
<button type="button" (click)="removeAccessControlItem(i)" class="btn btn-outline-danger">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</ng-container>
<button type="button" id="add-btn" class="btn btn-outline-primary mt-3"
(click)="addAccessControlItem()">
<i class="fas fa-plus"></i>
Add more
</button>
</form>

View File

@@ -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<AccessControlArrayFormComponent>;
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);
});
});

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}