[CST-4506] Address feedback

This commit is contained in:
Giuseppe Digilio
2022-01-24 19:54:21 +01:00
parent 532d8fb9a3
commit 973ceb3b4b
13 changed files with 115 additions and 160 deletions

View File

@@ -1,7 +1,5 @@
import { autoserialize } from 'cerialize';
/** /**
* Model class for an Access Condition * Model class for an Item Access Condition
*/ */
export class AccessesConditionOption { export class AccessesConditionOption {
@@ -15,7 +13,6 @@ export class AccessesConditionOption {
*/ */
groupName: string; groupName: string;
/** /**
* A boolean representing if this Access Condition has a start date * A boolean representing if this Access Condition has a start date
*/ */

View File

@@ -1,27 +1,39 @@
import { autoserialize, inheritSerialization, deserialize } from 'cerialize'; import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
import { typedObject, link } from '../../cache/builders/build-decorators'; import { typedObject } from '../../cache/builders/build-decorators';
import { ConfigObject } from './config.model'; import { ConfigObject } from './config.model';
import { AccessesConditionOption } from './config-accesses-conditions-options.model'; import { AccessesConditionOption } from './config-accesses-conditions-options.model';
import { SUBMISSION_ACCESSES_TYPE } from './config-type'; import { SUBMISSION_ACCESSES_TYPE } from './config-type';
import { HALLink } from '../../shared/hal-link.model'; import { HALLink } from '../../shared/hal-link.model';
/**
* Class for the configuration describing the item accesses condition
*/
@typedObject @typedObject
@inheritSerialization(ConfigObject) @inheritSerialization(ConfigObject)
export class SubmissionAccessModel extends ConfigObject { export class SubmissionAccessModel extends ConfigObject {
static type = SUBMISSION_ACCESSES_TYPE; static type = SUBMISSION_ACCESSES_TYPE;
/** /**
* A list of available bitstream access conditions * A list of available item access conditions
*/ */
@autoserialize @autoserialize
accessConditionOptions: AccessesConditionOption[]; accessConditionOptions: AccessesConditionOption[];
/**
* Boolean that indicates whether the current item must be findable via search or browse.
*/
@autoserialize @autoserialize
discoverable: boolean; discoverable: boolean;
/**
* Boolean that indicates whether or not the user can change the discoverable flag.
*/
@autoserialize @autoserialize
canChangeDiscoverable: boolean; canChangeDiscoverable: boolean;
/**
* The links to all related resources returned by the rest api.
*/
@deserialize @deserialize
_links: { _links: {
self: HALLink self: HALLink

View File

@@ -0,0 +1,25 @@
/**
* An interface to represent an access condition.
*/
export class AccessConditionObject {
/**
* The access condition id
*/
id: string;
/**
* The access condition name
*/
name: string;
/**
* Possible start date of the access condition
*/
startDate: string;
/**
* Possible end date of the access condition
*/
endDate: string;
}

View File

@@ -1,37 +0,0 @@
import { autoserialize, inheritSerialization } from 'cerialize';
import { typedObject } from '../../cache/builders/build-decorators';
import { excludeFromEquals } from '../../utilities/equals.decorators';
import { ResourceType } from '../../shared/resource-type';
import { HALResource } from '../../shared/hal-resource.model';
import { SUBMISSION_ACCESSES } from './submission-accesses.resource-type';
@typedObject
@inheritSerialization(HALResource)
export class SubmissionAccesses extends HALResource {
static type = SUBMISSION_ACCESSES;
/**
* The object type
*/
@excludeFromEquals
@autoserialize
type: ResourceType;
@autoserialize
discoverable: boolean;
@autoserialize
accessConditions: AccessConditions[];
}
export interface AccessConditions {
name: string;
startDate?: Date;
hasStartDate?: boolean;
maxStartDate?: string;
hasEndDate?: boolean;
maxEndDate?: string;
endDate?: Date;
}

View File

@@ -1,7 +1,7 @@
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
/** /**
* The resource type for License * The resource type for Accesses section
* *
* Needs to be in a separate file to prevent circular * Needs to be in a separate file to prevent circular
* dependencies in webpack. * dependencies in webpack.

View File

@@ -0,0 +1,8 @@
import { AccessConditionObject } from './access-condition.model';
/**
* An interface to represent item's access condition.
*/
export class SubmissionItemAccessConditionObject extends AccessConditionObject {
}

View File

@@ -1,25 +1,8 @@
import { AccessConditionObject } from './access-condition.model';
/** /**
* An interface to represent bitstream's access condition. * An interface to represent bitstream's access condition.
*/ */
export class SubmissionUploadFileAccessConditionObject { export class SubmissionUploadFileAccessConditionObject extends AccessConditionObject {
/**
* The access condition id
*/
id: string;
/**
* The access condition name
*/
name: string;
/**
* Possible start date of the access condition
*/
startDate: string;
/**
* Possible end date of the access condition
*/
endDate: string;
} }

View File

@@ -1,18 +1,21 @@
import { SubmissionItemAccessConditionObject } from './submission-item-access-condition.model';
/** /**
* An interface to represent the submission's creative commons license section data. * An interface to represent the submission's item accesses condition.
*/ */
export interface WorkspaceitemSectionAccessesObject { export interface WorkspaceitemSectionAccessesObject {
/**
* The access condition id
*/
id: string; id: string;
/**
* Boolean that indicates whether the current item must be findable via search or browse.
*/
discoverable: boolean; discoverable: boolean;
accessConditions: [
{ /**
name: string; * A list of available item access conditions
startDate?: Date; */
hasStartDate?: boolean; accessConditions: SubmissionItemAccessConditionObject[];
maxStartDate?: string;
hasEndDate?: boolean;
maxEndDate?: string;
endDate?: Date;
}
];
} }

View File

@@ -14,7 +14,8 @@
<div> <div>
<ng-container #componentViewContainer></ng-container> <ng-container #componentViewContainer></ng-container>
</div> </div>
<small *ngIf="hasHint && ((model.repeatable === false && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)"
<small *ngIf="hasHint && ((!model.repeatable && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)"
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small> class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
<!-- In case of repeatable fields show empty space for all elements except the first --> <!-- In case of repeatable fields show empty space for all elements except the first -->
<div *ngIf="context?.index !== null <div *ngIf="context?.index !== null

View File

@@ -1,11 +1,15 @@
import { SectionAccessesService } from './section-accesses.service'; import { SectionAccessesService } from './section-accesses.service';
import { Component, Inject, ViewChild } from '@angular/core'; import { Component, Inject, ViewChild } from '@angular/core';
import { filter, map, mergeMap, take } from 'rxjs/operators';
import { combineLatest, Observable, of, Subscription } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { renderSectionFor } from '../sections-decorator'; import { renderSectionFor } from '../sections-decorator';
import { SectionsType } from '../sections-type'; import { SectionsType } from '../sections-type';
import { SectionDataObject } from '../models/section-data.model'; import { SectionDataObject } from '../models/section-data.model';
import { SectionsService } from '../sections.service'; import { SectionsService } from '../sections.service';
import { SectionModelComponent } from '../models/section.model'; import { SectionModelComponent } from '../models/section.model';
import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs';
import { import {
DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX, DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX,
DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER, DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER,
@@ -19,13 +23,15 @@ import {
MATCH_ENABLED, MATCH_ENABLED,
OR_OPERATOR OR_OPERATOR
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
import { import {
ACCESS_CONDITION_GROUP_CONFIG, ACCESS_CONDITION_GROUP_CONFIG,
ACCESS_CONDITION_GROUP_LAYOUT, ACCESS_CONDITION_GROUP_LAYOUT,
ACCESS_CONDITIONS_FORM_ARRAY_CONFIG, ACCESS_CONDITIONS_FORM_ARRAY_CONFIG,
ACCESS_CONDITIONS_FORM_ARRAY_LAYOUT, ACCESS_CONDITIONS_FORM_ARRAY_LAYOUT,
ACCESS_FORM_CHECKBOX_CONFIG,
ACCESS_FORM_CHECKBOX_LAYOUT,
FORM_ACCESS_CONDITION_END_DATE_CONFIG, FORM_ACCESS_CONDITION_END_DATE_CONFIG,
FORM_ACCESS_CONDITION_END_DATE_LAYOUT, FORM_ACCESS_CONDITION_END_DATE_LAYOUT,
FORM_ACCESS_CONDITION_START_DATE_CONFIG, FORM_ACCESS_CONDITION_START_DATE_CONFIG,
@@ -37,17 +43,18 @@ import { hasValue, isNotEmpty, isNotNull } from '../../../shared/empty.util';
import { WorkspaceitemSectionAccessesObject } from '../../../core/submission/models/workspaceitem-section-accesses.model'; import { WorkspaceitemSectionAccessesObject } from '../../../core/submission/models/workspaceitem-section-accesses.model';
import { SubmissionAccessesConfigService } from '../../../core/config/submission-accesses-config.service'; import { SubmissionAccessesConfigService } from '../../../core/config/submission-accesses-config.service';
import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
import { filter, map, mergeMap, take } from 'rxjs/operators';
import { FormComponent } from '../../../shared/form/form.component'; import { FormComponent } from '../../../shared/form/form.component';
import { FormService } from '../../../shared/form/form.service'; import { FormService } from '../../../shared/form/form.service';
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
import { SectionFormOperationsService } from '../form/section-form-operations.service'; import { SectionFormOperationsService } from '../form/section-form-operations.service';
import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder';
import { AccessesConditionOption } from '../../../core/config/models/config-accesses-conditions-options.model'; import { AccessesConditionOption } from '../../../core/config/models/config-accesses-conditions-options.model';
import { TranslateService } from '@ngx-translate/core';
import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service';
import { dateToISOFormat } from '../../../shared/date.util'; import { dateToISOFormat } from '../../../shared/date.util';
/**
* This component represents a section for managing item's access conditions.
*/
@Component({ @Component({
selector: 'ds-section-accesses', selector: 'ds-section-accesses',
templateUrl: './section-accesses.component.html', templateUrl: './section-accesses.component.html',
@@ -62,7 +69,7 @@ export class SubmissionSectionAccessesComponent extends SectionModelComponent {
@ViewChild('formRef') public formRef: FormComponent; @ViewChild('formRef') public formRef: FormComponent;
/** /**
* List of available access conditions that could be set to files * List of available access conditions that could be set to item
*/ */
public availableAccessConditionOptions: AccessesConditionOption[]; // List of accessConditions that an user can select public availableAccessConditionOptions: AccessesConditionOption[]; // List of accessConditions that an user can select
@@ -73,52 +80,34 @@ export class SubmissionSectionAccessesComponent extends SectionModelComponent {
public formId: string; public formId: string;
/** /**
* The accesses metadata data * The accesses section data
* @type {WorkspaceitemSectionAccessesObject} * @type {WorkspaceitemSectionAccessesObject}
*/ */
public accessesData: WorkspaceitemSectionAccessesObject; public accessesData: WorkspaceitemSectionAccessesObject;
/**
* The collection name this submission belonging to
* @type {string}
*/
public collectionName: string;
/**
* Is the upload required
* @type {boolean}
*/
public required$ = new BehaviorSubject<boolean>(true);
/** /**
* The form model * The form model
* @type {DynamicFormControlModel[]} * @type {DynamicFormControlModel[]}
*/ */
formModel: DynamicFormControlModel[]; public formModel: DynamicFormControlModel[];
/** /**
* Array to track all subscriptions and unsubscribe them onDestroy * Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array} * @type {Array}
*/ */
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
/** /**
* The [[JsonPatchOperationPathCombiner]] object * The [[JsonPatchOperationPathCombiner]] object
* @type {JsonPatchOperationPathCombiner} * @type {JsonPatchOperationPathCombiner}
*/ */
protected pathCombiner: JsonPatchOperationPathCombiner; protected pathCombiner: JsonPatchOperationPathCombiner;
/**
* A map representing all field prevous values
* @type {Map}
*/
protected previousValue: Map<string, number[]> = new Map();
/**
* A map representing all field on their way to be removed
* @type {Map}
*/
protected fieldsOnTheirWayToBeRemoved: Map<string, number[]> = new Map();
/** /**
* Defines if the access discoverable property can be managed * Defines if the access discoverable property can be managed
*/ */
public canChangeDiscoverable: boolean; public canChangeDiscoverable: boolean;
/** /**
* Initialize instance variables * Initialize instance variables
* *
@@ -233,36 +222,6 @@ export class SubmissionSectionAccessesComponent extends SectionModelComponent {
} }
} }
/**
* Check if the specified form field has already a value stored
*
* @param fieldId
* the section data retrieved from the serverù
* @param index
* the section data retrieved from the server
*/
hasStoredValue(fieldId, index): boolean {
if (isNotEmpty(this.sectionData.data)) {
return this.sectionData.data.hasOwnProperty(fieldId) &&
isNotEmpty(this.sectionData.data[fieldId][index]) &&
!this.isFieldToRemove(fieldId, index);
} else {
return false;
}
}
/**
* Check if the specified field is on the way to be removed
*
* @param fieldId
* the section data retrieved from the serverù
* @param index
* the section data retrieved from the server
*/
isFieldToRemove(fieldId, index) {
return this.fieldsOnTheirWayToBeRemoved.has(fieldId) && this.fieldsOnTheirWayToBeRemoved.get(fieldId).includes(index);
}
/** /**
* Unsubscribe from all subscriptions * Unsubscribe from all subscriptions
*/ */
@@ -286,7 +245,7 @@ export class SubmissionSectionAccessesComponent extends SectionModelComponent {
const accessData$ = this.accessesService.getAccessesData(this.submissionId, this.sectionData.id); const accessData$ = this.accessesService.getAccessesData(this.submissionId, this.sectionData.id);
combineLatest(config$, accessData$).subscribe(([config, accessData]) => { combineLatest([config$, accessData$]).subscribe(([config, accessData]) => {
this.availableAccessConditionOptions = isNotEmpty(config.accessConditionOptions) ? config.accessConditionOptions : []; this.availableAccessConditionOptions = isNotEmpty(config.accessConditionOptions) ? config.accessConditionOptions : [];
this.canChangeDiscoverable = !!config.canChangeDiscoverable; this.canChangeDiscoverable = !!config.canChangeDiscoverable;
this.accessesData = accessData; this.accessesData = accessData;
@@ -313,17 +272,16 @@ export class SubmissionSectionAccessesComponent extends SectionModelComponent {
const formModel: DynamicFormControlModel[] = []; const formModel: DynamicFormControlModel[] = [];
if (this.canChangeDiscoverable) { if (this.canChangeDiscoverable) {
const discoverableCheckboxConfig = Object.assign({}, ACCESS_FORM_CHECKBOX_CONFIG, {
label: this.translate.instant('submission.sections.accesses.form.discoverable-label'),
hint: this.translate.instant('submission.sections.accesses.form.discoverable-description'),
value: this.accessesData.discoverable
});
formModel.push( formModel.push(
new DynamicCheckboxModel({ new DynamicCheckboxModel(discoverableCheckboxConfig, ACCESS_FORM_CHECKBOX_LAYOUT)
id: 'discoverable',
label: this.translate.instant('submission.sections.accesses.form.discoverable-label'),
name: 'discoverable',
value: this.accessesData.discoverable
})
); );
} }
const accessConditionTypeModelConfig = Object.assign({}, FORM_ACCESS_CONDITION_TYPE_CONFIG); const accessConditionTypeModelConfig = Object.assign({}, FORM_ACCESS_CONDITION_TYPE_CONFIG);
const accessConditionsArrayConfig = Object.assign({}, ACCESS_CONDITIONS_FORM_ARRAY_CONFIG); const accessConditionsArrayConfig = Object.assign({}, ACCESS_CONDITIONS_FORM_ARRAY_CONFIG);
const accessConditionTypeOptions = []; const accessConditionTypeOptions = [];

View File

@@ -7,19 +7,22 @@ import {
MATCH_ENABLED, MATCH_ENABLED,
OR_OPERATOR, OR_OPERATOR,
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { DynamicCheckboxModelConfig } from '@ng-dynamic-forms/core/lib/model/checkbox/dynamic-checkbox.model';
export const ACCESS_FORM_CHECKBOX_LAYOUT: DynamicFormControlLayout = { export const ACCESS_FORM_CHECKBOX_CONFIG: DynamicCheckboxModelConfig = {
element: { id: 'discoverable',
host: 'form-group flex-fill access-condition-group', name: 'discoverable'
id: 'discoverable',
// disabled: false,
label: 'submission.sections.accesses.form.discoverable-label',
name: 'discoverable',
}
}; };
export const ACCESS_FORM_CHECKBOX_LAYOUT = {
element: {
container: 'custom-control custom-checkbox pl-1',
control: 'custom-control-input',
label: 'custom-control-label pt-1'
}
};
export const ACCESS_CONDITION_GROUP_CONFIG: DynamicFormGroupModelConfig = { export const ACCESS_CONDITION_GROUP_CONFIG: DynamicFormGroupModelConfig = {
id: 'accessConditionGroup', id: 'accessConditionGroup',
@@ -28,7 +31,7 @@ export const ACCESS_CONDITION_GROUP_CONFIG: DynamicFormGroupModelConfig = {
export const ACCESS_CONDITION_GROUP_LAYOUT: DynamicFormControlLayout = { export const ACCESS_CONDITION_GROUP_LAYOUT: DynamicFormControlLayout = {
element: { element: {
host: 'form-group flex-fill access-condition-group', host: 'form-group flex-fill',
container: 'pl-1 pr-1', container: 'pl-1 pr-1',
control: 'form-row ' control: 'form-row '
} }

View File

@@ -5,12 +5,12 @@ import { distinctUntilChanged, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { SubmissionState } from '../../submission.reducers'; import { SubmissionState } from '../../submission.reducers';
import { isUndefined } from 'util'; import { isNotUndefined } from '../../../shared/empty.util';
import { submissionSectionDataFromIdSelector } from '../../selectors'; import { submissionSectionDataFromIdSelector } from '../../selectors';
import { WorkspaceitemSectionAccessesObject } from '../../../core/submission/models/workspaceitem-section-accesses.model';
/** /**
* A service that provides methods to handle submission's bitstream state. * A service that provides methods to handle submission item's accesses condition state.
*/ */
@Injectable() @Injectable()
export class SectionAccessesService { export class SectionAccessesService {
@@ -24,7 +24,7 @@ export class SectionAccessesService {
/** /**
* Return bitstream's metadata * Return item's accesses condition state.
* *
* @param submissionId * @param submissionId
* The submission id * The submission id
@@ -33,10 +33,10 @@ export class SectionAccessesService {
* @returns {Observable} * @returns {Observable}
* Emits bitstream's metadata * Emits bitstream's metadata
*/ */
public getAccessesData(submissionId: string, sectionId: string): Observable<any> { public getAccessesData(submissionId: string, sectionId: string): Observable<WorkspaceitemSectionAccessesObject> {
return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe(
filter((state) => !isUndefined(state)), filter((state) => isNotUndefined(state)),
distinctUntilChanged()); distinctUntilChanged());
} }
} }

View File

@@ -3895,6 +3895,8 @@
"submission.sections.upload.upload-successful": "Upload successful", "submission.sections.upload.upload-successful": "Upload successful",
"submission.sections.accesses.form.discoverable-description": "When checked, this item will be discoverable in search/browse. When unchecked, the item will only be available via a direct link and will never appear in search/browse.",
"submission.sections.accesses.form.discoverable-label": "Discoverable", "submission.sections.accesses.form.discoverable-label": "Discoverable",