From cceea734b3f13ec94b6ee21d0da8550f68557d16 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 12 Dec 2022 11:43:56 +0100 Subject: [PATCH 01/81] 97248: Remove hardcoded 'default' configuration --- src/app/shared/search/search.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index c017a5065b..bc6b10dc8d 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -60,7 +60,7 @@ export class SearchComponent implements OnInit { * The configuration to use for the search options * If empty, 'default' is used */ - @Input() configuration = 'default'; + @Input() configuration; /** * The actual query for the fixed filter. From b00a0f5be9b28a4e44d35514503b4cf7acc22ec9 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 29 Mar 2023 17:56:48 +0200 Subject: [PATCH 02/81] 100553: Added extra regex validation to prevent users from adding namespaces, elements and qualifiers with spaces Minor fixes: - Metadata Registry's name field was not being emptied after successful submission - The first input from both forms both had the red error border after clearing the fields --- .../metadata-schema-form.component.spec.ts | 4 +++- .../metadata-schema-form.component.ts | 11 ++++++++--- .../metadata-field-form.component.spec.ts | 4 +++- .../metadata-field-form.component.ts | 15 +++++++++++++-- src/assets/i18n/en.json5 | 6 ++++++ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts index 8d416c2df8..98e98a6646 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts @@ -29,7 +29,9 @@ describe('MetadataSchemaFormComponent', () => { createFormGroup: () => { return { patchValue: () => { - } + }, + markAsUntouched(opts?: any) { + }, }; } }; diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts index 5c6885ae72..adb649696c 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts @@ -77,10 +77,10 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { } ngOnInit() { - combineLatest( + combineLatest([ this.translateService.get(`${this.messagePrefix}.name`), this.translateService.get(`${this.messagePrefix}.namespace`) - ).subscribe(([name, namespace]) => { + ]).subscribe(([name, namespace]) => { this.name = new DynamicInputModel({ id: 'name', label: name, @@ -97,8 +97,12 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { name: 'namespace', validators: { required: null, + pattern: '^[^.]*$', }, required: true, + errorMessages: { + pattern: 'error.validation.metadata.namespace.invalid-pattern', + }, }); this.formModel = [ new DynamicFormGroupModel( @@ -163,9 +167,10 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { * Reset all input-fields to be empty */ clearFields() { + this.formGroup.markAsUntouched(); this.formGroup.patchValue({ metadatadataschemagroup:{ - prefix: '', + name: '', namespace: '' } }); diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts index e13180d633..fcd8dd395d 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts @@ -39,7 +39,9 @@ describe('MetadataFieldFormComponent', () => { createFormGroup: () => { return { patchValue: () => { - } + }, + markAsUntouched(opts?: any) { + }, }; } }; diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts index 1c000c3c76..14013f3cb7 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts @@ -98,25 +98,35 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { * Initialize the component, setting up the necessary Models for the dynamic form */ ngOnInit() { - combineLatest( + combineLatest([ this.translateService.get(`${this.messagePrefix}.element`), this.translateService.get(`${this.messagePrefix}.qualifier`), this.translateService.get(`${this.messagePrefix}.scopenote`) - ).subscribe(([element, qualifier, scopenote]) => { + ]).subscribe(([element, qualifier, scopenote]) => { this.element = new DynamicInputModel({ id: 'element', label: element, name: 'element', validators: { required: null, + pattern: '^[^.]*$', }, required: true, + errorMessages: { + pattern: 'error.validation.metadata.element.invalid-pattern', + }, }); this.qualifier = new DynamicInputModel({ id: 'qualifier', label: qualifier, name: 'qualifier', + validators: { + pattern: '^[^.]*$', + }, required: false, + errorMessages: { + pattern: 'error.validation.metadata.qualifier.invalid-pattern', + }, }); this.scopeNote = new DynamicInputModel({ id: 'scopeNote', @@ -189,6 +199,7 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { * Reset all input-fields to be empty */ clearFields() { + this.formGroup.markAsUntouched(); this.formGroup.patchValue({ metadatadatafieldgroup: { element: '', diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 597f226cc7..c0edb83a3b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1478,6 +1478,12 @@ "error.validation.groupExists": "This group already exists", + "error.validation.metadata.namespace.invalid-pattern": "This field cannot contain dots, please use the Element & Qualifier fields instead", + + "error.validation.metadata.element.invalid-pattern": "This field cannot contain dots, please use the Qualifier field instead", + + "error.validation.metadata.qualifier.invalid-pattern": "This field cannot contain dots", + "feed.description": "Syndication feed", From 792a614631dcc6e915ea0301355f06b387a3db4f Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 30 Mar 2023 17:09:50 +0200 Subject: [PATCH 03/81] 100553: Set the maximum amount of metadata fields shown on EditInPlaceFieldComponent back to 10 --- .../edit-in-place-field/edit-in-place-field.component.html | 2 +- .../edit-in-place-field.component.spec.ts | 2 +- .../edit-in-place-field/edit-in-place-field.component.ts | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html index 46299c1b08..64e863b8bf 100644 --- a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html +++ b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html @@ -12,7 +12,7 @@ (clickSuggestion)="update(suggestionControl)" (typeSuggestion)="update(suggestionControl)" (dsClickOutside)="checkValidity(suggestionControl)" - (findSuggestions)="findMetadataFieldSuggestions($event)" + (findSuggestions)="findMetadataFieldSuggestions($event, { elementsPerPage: 10 })" #suggestionControl="ngModel" [valid]="(valid | async) !== false" dsAutoFocus autoFocusSelector=".suggestion_input" diff --git a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts index 121ab4580e..2a2ca7ffaa 100644 --- a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts @@ -228,7 +228,7 @@ describe('EditInPlaceFieldComponent', () => { })); it('it should call queryMetadataFields on the metadataFieldService with the correct query', () => { - expect(metadataFieldService.queryMetadataFields).toHaveBeenCalledWith(query, null, true, false, followLink('schema')); + expect(metadataFieldService.queryMetadataFields).toHaveBeenCalledWith(query, {}, true, false, followLink('schema')); }); it('it should set metadataFieldSuggestions to the right value', () => { diff --git a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts index f1ebecb84f..6ed057bc6a 100644 --- a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts +++ b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts @@ -15,6 +15,7 @@ import { InputSuggestion } from '../../../../shared/input-suggestions/input-sugg import { followLink } from '../../../../shared/utils/follow-link-config.model'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; +import { FindListOptions } from '../../../../core/data/find-list-options.model'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -124,10 +125,11 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges { * Then sets all found metadata fields as metadataFieldSuggestions * Ignores fields from metadata schemas "relation" and "relationship" * @param query The query to look for + * @param options The options that need to be given to the backend */ - findMetadataFieldSuggestions(query: string) { + findMetadataFieldSuggestions(query: string, options: FindListOptions = {}) { if (isNotEmpty(query)) { - return this.registryService.queryMetadataFields(query, null, true, false, followLink('schema')).pipe( + return this.registryService.queryMetadataFields(query, options, true, false, followLink('schema')).pipe( getFirstSucceededRemoteData(), metadataFieldsToString(), ).subscribe((fieldNames: string[]) => { From 24e6cdd3ecbc30b9941f28564f9613be56a37928 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 3 Apr 2023 14:13:06 +0200 Subject: [PATCH 04/81] 100553: Removed possibility to updated schema name, element and qualifier --- .../metadata-schema-form.component.spec.ts | 27 ++++----- .../metadata-schema-form.component.ts | 49 ++++++++-------- .../metadata-field-form.component.spec.ts | 22 ++++--- .../metadata-field-form.component.ts | 58 +++++++++---------- 4 files changed, 75 insertions(+), 81 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts index 98e98a6646..b758767ddb 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; - import { MetadataSchemaFormComponent } from './metadata-schema-form.component'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { CommonModule } from '@angular/common'; @@ -30,7 +29,7 @@ describe('MetadataSchemaFormComponent', () => { return { patchValue: () => { }, - markAsUntouched(opts?: any) { + reset(_value?: any, _options?: { onlySelf?: boolean; emitEvent?: boolean; }): void { }, }; } @@ -38,7 +37,7 @@ describe('MetadataSchemaFormComponent', () => { /* eslint-enable no-empty, @typescript-eslint/no-empty-function */ beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + return TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [MetadataSchemaFormComponent, EnumKeysPipe], providers: [ @@ -66,7 +65,7 @@ describe('MetadataSchemaFormComponent', () => { const expected = Object.assign(new MetadataSchema(), { namespace: namespace, prefix: prefix - }); + } as MetadataSchema); beforeEach(() => { spyOn(component.submitForm, 'emit'); @@ -81,11 +80,10 @@ describe('MetadataSchemaFormComponent', () => { fixture.detectChanges(); }); - it('should emit a new schema using the correct values', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.submitForm.emit).toHaveBeenCalledWith(expected); - }); - })); + it('should emit a new schema using the correct values', async () => { + await fixture.whenStable(); + expect(component.submitForm.emit).toHaveBeenCalledWith(expected); + }); }); describe('with an active schema', () => { @@ -93,7 +91,7 @@ describe('MetadataSchemaFormComponent', () => { id: 1, namespace: namespace, prefix: prefix - }); + } as MetadataSchema); beforeEach(() => { spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(expectedWithId)); @@ -101,11 +99,10 @@ describe('MetadataSchemaFormComponent', () => { fixture.detectChanges(); }); - it('should edit the existing schema using the correct values', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId); - }); - })); + it('should edit the existing schema using the correct values', async () => { + await fixture.whenStable(); + expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId); + }); }); }); }); diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts index adb649696c..b7c16bc83f 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts @@ -87,9 +87,12 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { name: 'name', validators: { required: null, - pattern: '^[^ ,_]{1,32}$' + pattern: '^[^. ,_]{1,32}$', }, required: true, + errorMessages: { + pattern: 'error.validation.metadata.namespace.invalid-pattern', + }, }); this.namespace = new DynamicInputModel({ id: 'namespace', @@ -97,12 +100,8 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { name: 'namespace', validators: { required: null, - pattern: '^[^.]*$', }, required: true, - errorMessages: { - pattern: 'error.validation.metadata.namespace.invalid-pattern', - }, }); this.formModel = [ new DynamicFormGroupModel( @@ -112,13 +111,18 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { }) ]; this.formGroup = this.formBuilderService.createFormGroup(this.formModel); - this.registryService.getActiveMetadataSchema().subscribe((schema) => { - this.formGroup.patchValue({ - metadatadataschemagroup:{ - name: schema != null ? schema.prefix : '', - namespace: schema != null ? schema.namespace : '' - } - }); + this.registryService.getActiveMetadataSchema().subscribe((schema: MetadataSchema) => { + if (schema == null) { + this.clearFields(); + } else { + this.formGroup.patchValue({ + metadatadataschemagroup: { + name: schema.prefix, + namespace: schema.namespace, + }, + }); + this.name.disabled = true; + } }); }); } @@ -136,10 +140,10 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { * When the schema has no id attached -> Create new schema * Emit the updated/created schema using the EventEmitter submitForm */ - onSubmit() { + onSubmit(): void { this.registryService.clearMetadataSchemaRequests().subscribe(); this.registryService.getActiveMetadataSchema().pipe(take(1)).subscribe( - (schema) => { + (schema: MetadataSchema) => { const values = { prefix: this.name.value, namespace: this.namespace.value @@ -151,9 +155,9 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { } else { this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), schema, { id: schema.id, - prefix: (values.prefix ? values.prefix : schema.prefix), - namespace: (values.namespace ? values.namespace : schema.namespace) - })).subscribe((updatedSchema) => { + prefix: schema.prefix, + namespace: values.namespace, + })).subscribe((updatedSchema: MetadataSchema) => { this.submitForm.emit(updatedSchema); }); } @@ -166,14 +170,9 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { /** * Reset all input-fields to be empty */ - clearFields() { - this.formGroup.markAsUntouched(); - this.formGroup.patchValue({ - metadatadataschemagroup:{ - name: '', - namespace: '' - } - }); + clearFields(): void { + this.formGroup.reset('metadatadataschemagroup'); + this.name.disabled = false; } /** diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts index fcd8dd395d..ad7b54945d 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts @@ -40,7 +40,7 @@ describe('MetadataFieldFormComponent', () => { return { patchValue: () => { }, - markAsUntouched(opts?: any) { + reset(_value?: any, _options?: { onlySelf?: boolean; emitEvent?: boolean; }): void { }, }; } @@ -48,7 +48,7 @@ describe('MetadataFieldFormComponent', () => { /* eslint-enable no-empty, @typescript-eslint/no-empty-function */ beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + return TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [MetadataFieldFormComponent, EnumKeysPipe], providers: [ @@ -100,11 +100,10 @@ describe('MetadataFieldFormComponent', () => { fixture.detectChanges(); }); - it('should emit a new field using the correct values', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.submitForm.emit).toHaveBeenCalledWith(expected); - }); - })); + it('should emit a new field using the correct values', async () => { + await fixture.whenStable(); + expect(component.submitForm.emit).toHaveBeenCalledWith(expected); + }); }); describe('with an active field', () => { @@ -122,11 +121,10 @@ describe('MetadataFieldFormComponent', () => { fixture.detectChanges(); }); - it('should edit the existing field using the correct values', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId); - }); - })); + it('should edit the existing field using the correct values', async () => { + await fixture.whenStable(); + expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId); + }); }); }); }); diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts index 14013f3cb7..55950a8773 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts @@ -142,14 +142,20 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { }) ]; this.formGroup = this.formBuilderService.createFormGroup(this.formModel); - this.registryService.getActiveMetadataField().subscribe((field) => { - this.formGroup.patchValue({ - metadatadatafieldgroup: { - element: field != null ? field.element : '', - qualifier: field != null ? field.qualifier : '', - scopeNote: field != null ? field.scopeNote : '' - } - }); + this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => { + if (field == null) { + this.clearFields(); + } else { + this.formGroup.patchValue({ + metadatadatafieldgroup: { + element: field.element, + qualifier: field.qualifier, + scopeNote: field.scopeNote, + }, + }); + this.element.disabled = true; + this.qualifier.disabled = true; + } }); }); } @@ -167,25 +173,24 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { * When the field has no id attached -> Create new field * Emit the updated/created field using the EventEmitter submitForm */ - onSubmit() { + onSubmit(): void { this.registryService.getActiveMetadataField().pipe(take(1)).subscribe( - (field) => { - const values = { - element: this.element.value, - qualifier: this.qualifier.value, - scopeNote: this.scopeNote.value - }; + (field: MetadataField) => { if (field == null) { - this.registryService.createMetadataField(Object.assign(new MetadataField(), values), this.metadataSchema).subscribe((newField) => { + this.registryService.createMetadataField(Object.assign(new MetadataField(), { + element: this.element.value, + qualifier: this.qualifier.value, + scopeNote: this.scopeNote.value, + }), this.metadataSchema).subscribe((newField: MetadataField) => { this.submitForm.emit(newField); }); } else { this.registryService.updateMetadataField(Object.assign(new MetadataField(), field, { id: field.id, - element: (values.element ? values.element : field.element), - qualifier: (values.qualifier ? values.qualifier : field.qualifier), - scopeNote: (values.scopeNote ? values.scopeNote : field.scopeNote) - })).subscribe((updatedField) => { + element: field.element, + qualifier: field.qualifier, + scopeNote: this.scopeNote.value, + })).subscribe((updatedField: MetadataField) => { this.submitForm.emit(updatedField); }); } @@ -198,15 +203,10 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { /** * Reset all input-fields to be empty */ - clearFields() { - this.formGroup.markAsUntouched(); - this.formGroup.patchValue({ - metadatadatafieldgroup: { - element: '', - qualifier: '', - scopeNote: '' - } - }); + clearFields(): void { + this.formGroup.reset('metadatadatafieldgroup'); + this.element.disabled = false; + this.qualifier.disabled = false; } /** From f4efe00671aed5cbff39571d45cf5ce49d60c631 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Fri, 14 Apr 2023 17:02:06 +0200 Subject: [PATCH 05/81] 101127: Create BrowseByTaxonomyPageComponent --- src/app/browse-by/browse-by-routing.module.ts | 8 ++++++ .../browse-by-taxonomy-page.component.html | 1 + .../browse-by-taxonomy-page.component.scss | 0 .../browse-by-taxonomy-page.component.spec.ts | 25 +++++++++++++++++++ .../browse-by-taxonomy-page.component.ts | 13 ++++++++++ src/app/browse-by/browse-by.module.ts | 6 ++++- 6 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html create mode 100644 src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss create mode 100644 src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts create mode 100644 src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts diff --git a/src/app/browse-by/browse-by-routing.module.ts b/src/app/browse-by/browse-by-routing.module.ts index 5788d3cc70..0bf61957ef 100644 --- a/src/app/browse-by/browse-by-routing.module.ts +++ b/src/app/browse-by/browse-by-routing.module.ts @@ -4,6 +4,7 @@ import { BrowseByGuard } from './browse-by-guard'; import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver'; import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver'; import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; +import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component'; @NgModule({ imports: [ @@ -12,6 +13,13 @@ import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-bro path: '', resolve: { breadcrumb: BrowseByDSOBreadcrumbResolver }, children: [ + { + path: 'srsc', + component: BrowseByTaxonomyPageComponent, + canActivate: [BrowseByGuard], + resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver }, + data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata' } + }, { path: ':id', component: ThemedBrowseBySwitcherComponent, diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html new file mode 100644 index 0000000000..04f39bf727 --- /dev/null +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html @@ -0,0 +1 @@ +

browse-by-taxonomy-page works!

diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts new file mode 100644 index 0000000000..b1ce257ef6 --- /dev/null +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component'; + +describe('BrowseByTaxonomyPageComponent', () => { + let component: BrowseByTaxonomyPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BrowseByTaxonomyPageComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BrowseByTaxonomyPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts new file mode 100644 index 0000000000..97402843f5 --- /dev/null +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-browse-by-taxonomy-page', + templateUrl: './browse-by-taxonomy-page.component.html', + styleUrls: ['./browse-by-taxonomy-page.component.scss'] +}) +/** + * Component for browsing items by metadata in a hierarchical controlled vocabulary + */ +export class BrowseByTaxonomyPageComponent { + +} diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts index 14e21f8b4c..8cd52c887d 100644 --- a/src/app/browse-by/browse-by.module.ts +++ b/src/app/browse-by/browse-by.module.ts @@ -5,11 +5,13 @@ import { SharedModule } from '../shared/shared.module'; import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse-by-metadata-page.component'; import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component'; import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component'; +import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component'; import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; import { ComcolModule } from '../shared/comcol/comcol.module'; import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component'; import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component'; import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component'; +import { FormModule } from '../shared/form/form.module'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -27,11 +29,13 @@ const ENTRY_COMPONENTS = [ imports: [ CommonModule, ComcolModule, - SharedModule + SharedModule, + FormModule, ], declarations: [ BrowseBySwitcherComponent, ThemedBrowseBySwitcherComponent, + BrowseByTaxonomyPageComponent, ...ENTRY_COMPONENTS ], exports: [ From 3da2b3c0ef9f8f47a2cb3ad79f5dd3d76a5030c4 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Mon, 17 Apr 2023 10:13:14 +0200 Subject: [PATCH 06/81] 101127: Put modal from VocabularyTreeview in VocabularyTreeviewModal --- .../models/onebox/dynamic-onebox.component.ts | 4 +- src/app/shared/form/form.module.ts | 4 +- .../vocabulary-treeview-modal.component.html | 15 ++ .../vocabulary-treeview-modal.component.scss | 0 ...ocabulary-treeview-modal.component.spec.ts | 25 ++++ .../vocabulary-treeview-modal.component.ts | 38 +++++ .../vocabulary-treeview.component.html | 136 ++++++++---------- .../vocabulary-treeview.component.ts | 9 +- .../search-hierarchy-filter.component.ts | 8 +- 9 files changed, 156 insertions(+), 83 deletions(-) create mode 100644 src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.html create mode 100644 src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.scss create mode 100644 src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.spec.ts create mode 100644 src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.ts diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts index 008328bf73..63eca80dfe 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts @@ -30,8 +30,8 @@ import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/ import { PageInfo } from '../../../../../../core/shared/page-info.model'; import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component'; import { Vocabulary } from '../../../../../../core/submission/vocabularies/models/vocabulary.model'; -import { VocabularyTreeviewComponent } from '../../../../vocabulary-treeview/vocabulary-treeview.component'; import { VocabularyEntryDetail } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; +import { VocabularyTreeviewModalComponent } from '../../../../vocabulary-treeview-modal/vocabulary-treeview-modal.component'; /** * Component representing a onebox input field. @@ -222,7 +222,7 @@ export class DsDynamicOneboxComponent extends DsDynamicVocabularyComponent imple map((vocabulary: Vocabulary) => vocabulary.preloadLevel), take(1) ).subscribe((preloadLevel) => { - const modalRef: NgbModalRef = this.modalService.open(VocabularyTreeviewComponent, { size: 'lg', windowClass: 'treeview' }); + const modalRef: NgbModalRef = this.modalService.open(VocabularyTreeviewModalComponent, { size: 'lg', windowClass: 'treeview' }); modalRef.componentInstance.vocabularyOptions = this.model.vocabularyOptions; modalRef.componentInstance.preloadLevel = preloadLevel; modalRef.componentInstance.selectedItem = this.currentValue ? this.currentValue : ''; diff --git a/src/app/shared/form/form.module.ts b/src/app/shared/form/form.module.ts index 61fc7e3c39..6e3fc33832 100644 --- a/src/app/shared/form/form.module.ts +++ b/src/app/shared/form/form.module.ts @@ -33,6 +33,7 @@ import { AuthorityConfidenceStateDirective } from './directives/authority-confid import { SortablejsModule } from 'ngx-sortablejs'; import { VocabularyTreeviewComponent } from './vocabulary-treeview/vocabulary-treeview.component'; import { VocabularyTreeviewService } from './vocabulary-treeview/vocabulary-treeview.service'; +import { VocabularyTreeviewModalComponent } from './vocabulary-treeview-modal/vocabulary-treeview-modal.component'; import { FormBuilderService } from './builder/form-builder.service'; import { DsDynamicTypeBindRelationService } from './builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service'; import { FormService } from './form.service'; @@ -67,7 +68,8 @@ const COMPONENTS = [ ChipsComponent, NumberPickerComponent, VocabularyTreeviewComponent, - ThemedExternalSourceEntryImportModalComponent + VocabularyTreeviewModalComponent, + ThemedExternalSourceEntryImportModalComponent, ]; const DIRECTIVES = [ diff --git a/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.html b/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.html new file mode 100644 index 0000000000..55f5ab5092 --- /dev/null +++ b/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.html @@ -0,0 +1,15 @@ + + diff --git a/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.scss b/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.spec.ts b/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.spec.ts new file mode 100644 index 0000000000..989375fd13 --- /dev/null +++ b/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VocabularyTreeviewModalComponent } from './vocabulary-treeview-modal.component'; + +describe('VocabularyTreeviewModalComponent', () => { + let component: VocabularyTreeviewModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ VocabularyTreeviewModalComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(VocabularyTreeviewModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.ts b/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.ts new file mode 100644 index 0000000000..48ff82f499 --- /dev/null +++ b/src/app/shared/form/vocabulary-treeview-modal/vocabulary-treeview-modal.component.ts @@ -0,0 +1,38 @@ +import { Component, Input } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { VocabularyOptions } from '../../../core/submission/vocabularies/models/vocabulary-options.model'; + +@Component({ + selector: 'ds-vocabulary-treeview-modal', + templateUrl: './vocabulary-treeview-modal.component.html', + styleUrls: ['./vocabulary-treeview-modal.component.scss'] +}) +/** + * Component that contains a modal to display a VocabularyTreeviewComponent + */ +export class VocabularyTreeviewModalComponent { + + /** + * The {@link VocabularyOptions} object + */ + @Input() vocabularyOptions: VocabularyOptions; + + /** + * Representing how many tree level load at initialization + */ + @Input() preloadLevel = 2; + + /** + * The vocabulary entry already selected, if any + */ + @Input() selectedItem: any = null; + + /** + * Initialize instance variables + * + * @param {NgbActiveModal} activeModal + */ + constructor( + public activeModal: NgbActiveModal, + ) { } +} diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html index 39c62d6e53..18a61b73d7 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -1,77 +1,67 @@ - - diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index 459442aabc..c534c5e205 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -25,16 +25,27 @@ export class BrowseByTaxonomyPageComponent implements OnInit { /** * The query parameters, contain the selected entries */ - queryParams: { 'f.subject': string[] }; + filterValues: string[]; ngOnInit() { this.vocabularyOptions = { name: 'srsc', closed: true }; } + /** + * Adds detail to selectedItems, transforms it to be used as query parameter + * and adds that to filterValues. If they already contained the detail, + * it gets deleted from both arrays. + * + * @param detail VocabularyEntryDetail to be added/deleted + */ onSelect(detail: VocabularyEntryDetail): void { - this.selectedItems.push(detail); - const filterValues = this.selectedItems - .map((item: VocabularyEntryDetail) => `${item.value},equals`); - this.queryParams = { 'f.subject': filterValues }; + if (!this.selectedItems.includes(detail)) { + this.selectedItems.push(detail); + this.filterValues = this.selectedItems + .map((item: VocabularyEntryDetail) => `${item.value},equals`); + } else { + this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry !== detail; }); + this.filterValues = this.filterValues.filter((value: string) => { return value !== `${detail.value},equals`; }); + } } } diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html index 2de1d375cb..0c90ae473b 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -33,8 +33,8 @@ container="body" (click)="onSelect(node.item)"> - - + + {{node.item.display}} @@ -57,8 +57,8 @@ container="body" (click)="onSelect(node.item)"> - - + + {{node.item.display}} diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts index 29e80cfc94..e5f5145e0a 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts @@ -264,14 +264,14 @@ describe('VocabularyTreeviewComponent test suite', () => { it('should not display checkboxes by default', async () => { fixture.detectChanges(); - expect(de.query(By.css('input[checkbox]'))).toBeNull(); + expect(de.query(By.css('.form-check-input'))).toBeNull(); expect(de.queryAll(By.css('cdk-tree-node')).length).toEqual(3); }); it('should display checkboxes if multiSelect is true', async () => { comp.multiSelect = true; fixture.detectChanges(); - expect(de.queryAll(By.css('#leaf-node-checkbox')).length).toEqual(3); + expect(de.queryAll(By.css('.form-check-input')).length).toEqual(3); expect(de.queryAll(By.css('cdk-tree-node')).length).toEqual(3); }); }); diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts index 572074d644..01225c638a 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts @@ -245,8 +245,12 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit { * Method called on entry select */ onSelect(item: VocabularyEntryDetail) { - this.selectedItems.push(item.id); - this.select.emit(item); + if (!this.selectedItems.includes(item.id)) { + this.selectedItems.push(item.id); + this.select.emit(item); + } else { + this.selectedItems = this.selectedItems.filter((detail: string) => { return detail !== item.id; }); + } } /** From 14b053b70418537a412795545ba6ad933feae779 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 26 Apr 2023 17:46:10 +0200 Subject: [PATCH 16/81] 101353: Add deselect Output in VocabularyTreeview --- .../browse-by-taxonomy-page.component.html | 3 ++- .../browse-by-taxonomy-page.component.ts | 15 +++++++-------- .../vocabulary-treeview.component.ts | 7 +++++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html index f84e3d72b1..149e1e6b33 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html @@ -2,7 +2,8 @@
+ (select)="onSelect($event)" + (deselect)="onDeselect($event)">
{{ 'browse.taxonomy.button' | translate }} diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index c534c5e205..b132f299d6 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -33,19 +33,18 @@ export class BrowseByTaxonomyPageComponent implements OnInit { /** * Adds detail to selectedItems, transforms it to be used as query parameter - * and adds that to filterValues. If they already contained the detail, - * it gets deleted from both arrays. + * and adds that to filterValues. * - * @param detail VocabularyEntryDetail to be added/deleted + * @param detail VocabularyEntryDetail to be added */ onSelect(detail: VocabularyEntryDetail): void { - if (!this.selectedItems.includes(detail)) { this.selectedItems.push(detail); this.filterValues = this.selectedItems .map((item: VocabularyEntryDetail) => `${item.value},equals`); - } else { - this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry !== detail; }); - this.filterValues = this.filterValues.filter((value: string) => { return value !== `${detail.value},equals`; }); - } + } + + onDeselect(detail: VocabularyEntryDetail): void { + this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry !== detail; }); + this.filterValues = this.filterValues.filter((value: string) => { return value !== `${detail.value},equals`; }); } } diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts index 01225c638a..2199de920e 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts @@ -94,6 +94,12 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit { */ @Output() select: EventEmitter = new EventEmitter(null); + /** + * An event fired when a vocabulary entry is deselected. + * Event's payload equals to {@link VocabularyEntryDetail} deselected. + */ + @Output() deselect: EventEmitter = new EventEmitter(null); + /** * A boolean representing if user is authenticated */ @@ -250,6 +256,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit { this.select.emit(item); } else { this.selectedItems = this.selectedItems.filter((detail: string) => { return detail !== item.id; }); + this.deselect.emit(item); } } From bb242d99a6b7715203e2fcc96c1f9512173773a3 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 26 Apr 2023 18:37:55 +0200 Subject: [PATCH 17/81] 101353: Fix VocabularyTreeview onSelect being called twice --- .../vocabulary-treeview.component.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html index 0c90ae473b..82d8077dbc 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -33,7 +33,9 @@ container="body" (click)="onSelect(node.item)"> - + + {{node.item.display}} @@ -57,7 +59,9 @@ container="body" (click)="onSelect(node.item)"> - + + {{node.item.display}} From ea297d1296c4ee685a57e19efbf3c01fdd69ce06 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 26 Apr 2023 18:40:38 +0200 Subject: [PATCH 18/81] 101353: Add tests for BrowseByTaxonomyPageComponent --- .../browse-by-taxonomy-page.component.spec.ts | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts index b1ce257ef6..bc9380d7ad 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts @@ -1,14 +1,21 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component'; +import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('BrowseByTaxonomyPageComponent', () => { let component: BrowseByTaxonomyPageComponent; let fixture: ComponentFixture; + let detail1: VocabularyEntryDetail; + let detail2: VocabularyEntryDetail; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ BrowseByTaxonomyPageComponent ] + imports: [ TranslateModule.forRoot() ], + declarations: [ BrowseByTaxonomyPageComponent ], + schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); }); @@ -17,9 +24,47 @@ describe('BrowseByTaxonomyPageComponent', () => { fixture = TestBed.createComponent(BrowseByTaxonomyPageComponent); component = fixture.componentInstance; fixture.detectChanges(); + detail1 = new VocabularyEntryDetail(); + detail2 = new VocabularyEntryDetail(); + detail1.value = 'HUMANITIES and RELIGION'; + detail2.value = 'TECHNOLOGY'; }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should handle select event', () => { + component.onSelect(detail1); + expect(component.selectedItems.length).toBe(1); + expect(component.selectedItems).toContain(detail1); + expect(component.selectedItems.length).toBe(1); + expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals'] ); + }); + + it('should handle select event with multiple selected items', () => { + component.onSelect(detail1); + component.onSelect(detail2); + expect(component.selectedItems.length).toBe(2); + expect(component.selectedItems).toContain(detail1, detail2); + expect(component.selectedItems.length).toBe(2); + expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'] ); + }); + + it('should handle deselect event', () => { + component.onSelect(detail1); + component.onSelect(detail2); + expect(component.selectedItems.length).toBe(2); + expect(component.selectedItems.length).toBe(2); + component.onDeselect(detail1); + expect(component.selectedItems.length).toBe(1); + expect(component.selectedItems).toContain(detail2); + expect(component.selectedItems.length).toBe(1); + expect(component.filterValues).toEqual(['TECHNOLOGY,equals'] ); + }); + + afterEach(() => { + fixture.destroy(); + component = null; + }); }); From 09f3dddde4c761ac24d1c2b3d8b6fbaa594e8afd Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 27 Apr 2023 10:52:51 +0200 Subject: [PATCH 19/81] 101393: Fix breadcrumbs on BrowseByTaxonomyPage --- src/app/browse-by/browse-by-routing.module.ts | 5 +++-- src/assets/i18n/en.json5 | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/browse-by/browse-by-routing.module.ts b/src/app/browse-by/browse-by-routing.module.ts index 0bf61957ef..9e078dd4eb 100644 --- a/src/app/browse-by/browse-by-routing.module.ts +++ b/src/app/browse-by/browse-by-routing.module.ts @@ -5,6 +5,7 @@ import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolv import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver'; import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component'; +import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; @NgModule({ imports: [ @@ -17,8 +18,8 @@ import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse- path: 'srsc', component: BrowseByTaxonomyPageComponent, canActivate: [BrowseByGuard], - resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver }, - data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata' } + resolve: { breadcrumb: I18nBreadcrumbResolver }, + data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata.srsc' } }, { path: ':id', diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 5a0f5ce5d0..cf60499344 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -721,6 +721,8 @@ "browse.metadata.subject.breadcrumbs": "Browse by Subject", + "browse.metadata.srsc.breadcrumbs": "Browse by Subject Category", + "browse.metadata.title.breadcrumbs": "Browse by Title", "pagination.next.button": "Next", From da7c980ab1452ad5d7b0503d5389ef4adf748d37 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 27 Apr 2023 11:04:41 +0200 Subject: [PATCH 20/81] 101393: Add 'Browse by Subject Category' link to 'All of DSpace' --- src/app/menu.resolver.ts | 14 ++++++++++++++ src/assets/i18n/en.json5 | 2 ++ 2 files changed, 16 insertions(+) diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index 8630150c58..f771ef8b27 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -137,6 +137,20 @@ export class MenuResolver implements Resolve { } as TextMenuItemModel, } ); + menuList.push( + { + id: 'browse_global_by_srsc', + parentID: 'browse_global', + active: false, + visible: true, + index: 99, + model: { + type: MenuItemType.LINK, + text: `menu.section.browse_global_by_srsc`, + link: `/browse/srsc` + } as LinkMenuItemModel + } + ); } menuList.forEach((menuSection) => this.menuService.addSection(MenuID.PUBLIC, Object.assign(menuSection, { shouldPersistOnRouteChange: true diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index cf60499344..0958fc5d29 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2649,6 +2649,8 @@ "menu.section.browse_global_by_subject": "By Subject", + "menu.section.browse_global_by_srsc": "By Subject Category", + "menu.section.browse_global_by_title": "By Title", "menu.section.browse_global_communities_and_collections": "Communities & Collections", From a5c300aebdceb3d64ff37b65ae7a1a86b852bd29 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 27 Apr 2023 14:10:43 +0200 Subject: [PATCH 21/81] refactor mulitselect mode to use labels instead of buttons --- .../vocabulary-treeview.component.html | 63 ++++++++++++------- .../vocabulary-treeview.component.scss | 4 ++ 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html index 82d8077dbc..a2a0b28a5c 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -25,20 +25,28 @@ - @@ -51,21 +59,28 @@ aria-hidden="true"> - + + {{node.item.display}} + + diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.scss b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.scss index 39050ff85b..3f0cea10d2 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.scss +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.scss @@ -5,3 +5,7 @@ cdk-tree .btn:focus { box-shadow: none !important; } + +label { + cursor: pointer; +} From 2a84c425314aec536b4f37b8b7b0d18193600900 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 27 Apr 2023 14:19:14 +0200 Subject: [PATCH 22/81] 101393: Fix VocabularyTreeview tests + Remove template comments --- .../vocabulary-treeview/vocabulary-treeview.component.html | 2 -- .../vocabulary-treeview.component.spec.ts | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html index a2a0b28a5c..9f14795342 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -31,7 +31,6 @@ [openDelay]="500" container="body" > - - { })); afterEach(() => { - fixture.destroy(); + fixture?.destroy(); comp = null; compAsAny = null; }); @@ -264,14 +264,14 @@ describe('VocabularyTreeviewComponent test suite', () => { it('should not display checkboxes by default', async () => { fixture.detectChanges(); - expect(de.query(By.css('.form-check-input'))).toBeNull(); + expect(de.query(By.css('input[type=checkbox]'))).toBeNull(); expect(de.queryAll(By.css('cdk-tree-node')).length).toEqual(3); }); it('should display checkboxes if multiSelect is true', async () => { comp.multiSelect = true; fixture.detectChanges(); - expect(de.queryAll(By.css('.form-check-input')).length).toEqual(3); + expect(de.queryAll(By.css('input[type=checkbox]')).length).toEqual(3); expect(de.queryAll(By.css('cdk-tree-node')).length).toEqual(3); }); }); From 90a1f25ba9dce010f49c32b6b45ecd2db8fcfadc Mon Sep 17 00:00:00 2001 From: Alban Imami Date: Thu, 27 Apr 2023 16:51:41 +0200 Subject: [PATCH 23/81] Work for signposting --- server.ts | 9 +++ .../metadata-item.service.spec.ts | 16 +++++ .../metadata-item/metadata-item.service.ts | 70 +++++++++++++++++++ src/app/core/metadata/metadata.service.ts | 11 ++- src/app/init.service.ts | 3 + src/modules/app/browser-init.service.ts | 3 + src/modules/app/server-init.service.ts | 5 +- 7 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/app/core/metadata-item/metadata-item.service.spec.ts create mode 100644 src/app/core/metadata-item/metadata-item.service.ts diff --git a/server.ts b/server.ts index 3e10677a8b..5a3660e4de 100644 --- a/server.ts +++ b/server.ts @@ -180,6 +180,15 @@ export function app() { changeOrigin: true })); + /** + * Proxy the linksets + */ + router.use('/linksets**', createProxyMiddleware({ + target: `${environment.rest.baseUrl}/linksets`, + pathRewrite: path => path.replace(environment.ui.nameSpace, '/'), + changeOrigin: true + })); + /** * Checks if the rateLimiter property is present * When it is present, the rateLimiter will be enabled. When it is undefined, the rateLimiter will be disabled. diff --git a/src/app/core/metadata-item/metadata-item.service.spec.ts b/src/app/core/metadata-item/metadata-item.service.spec.ts new file mode 100644 index 0000000000..89ca15658d --- /dev/null +++ b/src/app/core/metadata-item/metadata-item.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { MetadataItemService } from './metadata-item.service'; + +describe('MetadataItemService', () => { + let service: MetadataItemService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MetadataItemService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/core/metadata-item/metadata-item.service.ts b/src/app/core/metadata-item/metadata-item.service.ts new file mode 100644 index 0000000000..a4fbcf587b --- /dev/null +++ b/src/app/core/metadata-item/metadata-item.service.ts @@ -0,0 +1,70 @@ +import { Inject, Injectable } from '@angular/core'; +import { MetadataService } from '../metadata/metadata.service'; +import { ActivatedRoute, NavigationEnd, Event as NavigationEvent, NavigationStart, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { Meta, Title } from '@angular/platform-browser'; +import { DSONameService } from '../breadcrumbs/dso-name.service'; +import { BundleDataService } from '../data/bundle-data.service'; +import { BitstreamDataService } from '../data/bitstream-data.service'; +import { BitstreamFormatDataService } from '../data/bitstream-format-data.service'; +import { RootDataService } from '../data/root-data.service'; +import { CoreState } from '../core-state.model'; +import { Store } from '@ngrx/store'; +import { HardRedirectService } from '../services/hard-redirect.service'; +import { APP_CONFIG, AppConfig } from 'src/config/app-config.interface'; +import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service'; +import { filter, map, switchMap, take, mergeMap } from 'rxjs/operators'; +import { DOCUMENT } from '@angular/common'; + +@Injectable({ + providedIn: 'root' +}) +export class MetadataItemService extends MetadataService { + + constructor( + private router1: ActivatedRoute, + router: Router, + translate: TranslateService, + meta: Meta, + title: Title, + dsoNameService: DSONameService, + bundleDataService: BundleDataService, + bitstreamDataService: BitstreamDataService, + bitstreamFormatDataService: BitstreamFormatDataService, + rootService: RootDataService, + store: Store, + hardRedirectService: HardRedirectService, + @Inject(APP_CONFIG) appConfig: AppConfig, + authorizationService: AuthorizationDataService, + @Inject(DOCUMENT) private document: Document + ) { + super(router, translate, meta, title, dsoNameService, bundleDataService, bitstreamDataService, bitstreamFormatDataService, rootService, store, hardRedirectService, appConfig, authorizationService); + } + + public checkCurrentRoute(){ + + console.log(this.router); + + this.router1.url.subscribe(url => { + console.log(url); + console.log(url[0].path); + }); + + // this.router.events.subscribe((event: NavigationEvent) => { + // if(event instanceof NavigationStart) { + // if(event.url.startsWith('/entities')){ + // console.log('We are on ENTITIES!'); + // } + // } + // }); + } + + setLinkTag(){ + this.clearMetaTags(); + + let link: HTMLLinkElement = this.document.createElement('link'); + link.setAttribute('rel', ''); + link.setAttribute('href', ''); + this.document.head.appendChild(link); + } +} diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 204c925e6b..c46f8b1d6e 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -84,7 +84,7 @@ export class MetadataService { ]; constructor( - private router: Router, + protected router: Router, private translate: TranslateService, private meta: Meta, private title: Title, @@ -363,6 +363,15 @@ export class MetadataService { } } + /** + * Add to the + */ + // private setLinkTag(): void { + // const value = this.getMetaTagValue('dc.link'); + // this.meta.addTag({ name: 'Link', content: value }); + // this.addMetaTag('Link', value); + // } + getBitLinkIfDownloadable(bitstream: Bitstream, bitstreamRd: RemoteData>): Observable { return observableOf(bitstream).pipe( getDownloadableBitstream(this.authorizationService), diff --git a/src/app/init.service.ts b/src/app/init.service.ts index 9fef2ca4ed..d5978d782d 100644 --- a/src/app/init.service.ts +++ b/src/app/init.service.ts @@ -24,6 +24,7 @@ import { isAuthenticationBlocking } from './core/auth/selectors'; import { distinctUntilChanged, find } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { MenuService } from './shared/menu/menu.service'; +import { MetadataItemService } from './core/metadata-item/metadata-item.service'; /** * Performs the initialization of the app. @@ -50,6 +51,7 @@ export abstract class InitService { protected localeService: LocaleService, protected angulartics2DSpace: Angulartics2DSpace, protected metadata: MetadataService, + protected metadataItem: MetadataItemService, protected breadcrumbsService: BreadcrumbsService, protected themeService: ThemeService, protected menuService: MenuService, @@ -188,6 +190,7 @@ export abstract class InitService { this.breadcrumbsService.listenForRouteChanges(); this.themeService.listenForRouteChanges(); this.menuService.listenForRouteChanges(); + this.metadataItem.checkCurrentRoute(); } /** diff --git a/src/modules/app/browser-init.service.ts b/src/modules/app/browser-init.service.ts index 61d57f10f9..f38883be1e 100644 --- a/src/modules/app/browser-init.service.ts +++ b/src/modules/app/browser-init.service.ts @@ -32,6 +32,7 @@ import { logStartupMessage } from '../../../startup-message'; import { MenuService } from '../../app/shared/menu/menu.service'; import { RootDataService } from '../../app/core/data/root-data.service'; import { firstValueFrom, Subscription } from 'rxjs'; +import { MetadataItemService } from 'src/app/core/metadata-item/metadata-item.service'; /** * Performs client-side initialization. @@ -51,6 +52,7 @@ export class BrowserInitService extends InitService { protected angulartics2DSpace: Angulartics2DSpace, protected googleAnalyticsService: GoogleAnalyticsService, protected metadata: MetadataService, + protected metadataItem: MetadataItemService, protected breadcrumbsService: BreadcrumbsService, protected klaroService: KlaroService, protected authService: AuthService, @@ -66,6 +68,7 @@ export class BrowserInitService extends InitService { localeService, angulartics2DSpace, metadata, + metadataItem, breadcrumbsService, themeService, menuService, diff --git a/src/modules/app/server-init.service.ts b/src/modules/app/server-init.service.ts index d909bb0e8d..074efc31e7 100644 --- a/src/modules/app/server-init.service.ts +++ b/src/modules/app/server-init.service.ts @@ -21,6 +21,7 @@ import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service'; import { ThemeService } from '../../app/shared/theme-support/theme.service'; import { take } from 'rxjs/operators'; import { MenuService } from '../../app/shared/menu/menu.service'; +import { MetadataItemService } from 'src/app/core/metadata-item/metadata-item.service'; /** * Performs server-side initialization. @@ -36,9 +37,10 @@ export class ServerInitService extends InitService { protected localeService: LocaleService, protected angulartics2DSpace: Angulartics2DSpace, protected metadata: MetadataService, + protected metadataItem: MetadataItemService, protected breadcrumbsService: BreadcrumbsService, protected themeService: ThemeService, - protected menuService: MenuService, + protected menuService: MenuService ) { super( store, @@ -48,6 +50,7 @@ export class ServerInitService extends InitService { localeService, angulartics2DSpace, metadata, + metadataItem, breadcrumbsService, themeService, menuService, From 89eb4e3cb2a8a07f45ee939f74387f1624c4e0c5 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 3 May 2023 15:29:12 +0200 Subject: [PATCH 24/81] [DURACOM-131] Show output files in separate lines --- src/app/process-page/detail/process-detail.component.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index 29cbfc113f..8825c71cee 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -17,10 +17,12 @@
+
- {{getFileName(file)}} - ({{(file?.sizeBytes) | dsFileSize }}) + {{getFileName(file)}} + ({{(file?.sizeBytes) | dsFileSize }}) +
From c066bc9d54e7ea0c9256ffedc7bb02b4ff2ee6b9 Mon Sep 17 00:00:00 2001 From: Enea Jahollari Date: Wed, 3 May 2023 17:22:20 +0200 Subject: [PATCH 25/81] [DURACOM-131] Added automatic refresh every 5 seconds if the status is running --- .../detail/process-detail.component.html | 19 ++-- .../detail/process-detail.component.ts | 102 ++++++++++++++---- 2 files changed, 93 insertions(+), 28 deletions(-) diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index 8825c71cee..9e7a24d3af 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -1,10 +1,15 @@ -
-
-

{{'process.detail.title' | translate:{ - id: process?.processId, - name: process?.scriptName - } }}

+
+
+
+

+ {{ 'process.detail.title' | translate:{ id: process?.processId, name: process?.scriptName } }} +

+
+
+ Refreshing in {{ seconds }}s +
+
{{ process?.scriptName }}
@@ -72,7 +77,7 @@ -
+
-
+
Refreshing in {{ seconds }}s
diff --git a/src/app/process-page/detail/process-detail.component.spec.ts b/src/app/process-page/detail/process-detail.component.spec.ts index 8749553eae..9552f9a092 100644 --- a/src/app/process-page/detail/process-detail.component.spec.ts +++ b/src/app/process-page/detail/process-detail.component.spec.ts @@ -35,6 +35,7 @@ import { NotificationsServiceStub } from '../../shared/testing/notifications-ser import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { getProcessListRoute } from '../process-page-routing.paths'; +import {ProcessStatus} from '../processes/process-status.model'; describe('ProcessDetailComponent', () => { let component: ProcessDetailComponent; @@ -44,6 +45,7 @@ describe('ProcessDetailComponent', () => { let nameService: DSONameService; let bitstreamDataService: BitstreamDataService; let httpClient: HttpClient; + let route: ActivatedRoute; let process: Process; let fileName: string; @@ -106,7 +108,8 @@ describe('ProcessDetailComponent', () => { }); processService = jasmine.createSpyObj('processService', { getFiles: createSuccessfulRemoteDataObject$(createPaginatedList(files)), - delete: createSuccessfulRemoteDataObject$(null) + delete: createSuccessfulRemoteDataObject$(null), + findById: createSuccessfulRemoteDataObject$(process), }); bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', { findByHref: createSuccessfulRemoteDataObject$(logBitstream) @@ -127,6 +130,13 @@ describe('ProcessDetailComponent', () => { router = jasmine.createSpyObj('router', { navigateByUrl:{} }); + + route = jasmine.createSpyObj('route', { + data: observableOf({ process: createSuccessfulRemoteDataObject(process) }), + snapshot: { + params: { id: process.processId } + } + }); } beforeEach(waitForAsync(() => { @@ -263,4 +273,92 @@ describe('ProcessDetailComponent', () => { }); }); + describe('refresh counter', () => { + const queryRefreshCounter = () => fixture.debugElement.query(By.css('.refresh-counter')); + + describe('if process is completed', () => { + beforeEach(() => { + process.processStatus = ProcessStatus.COMPLETED; + route.data = observableOf({process: createSuccessfulRemoteDataObject(process)}); + }); + + it('should not show', () => { + spyOn(component, 'startRefreshTimer'); + + const refreshCounter = queryRefreshCounter(); + expect(refreshCounter).toBeNull(); + + expect(component.startRefreshTimer).not.toHaveBeenCalled(); + }); + }); + + describe('if process is not finished', () => { + beforeEach(() => { + process.processStatus = ProcessStatus.RUNNING; + route.data = observableOf({process: createSuccessfulRemoteDataObject(process)}); + fixture.detectChanges(); + component.stopRefreshTimer(); + }); + + it('should call startRefreshTimer', () => { + spyOn(component, 'startRefreshTimer'); + + component.ngOnInit(); + fixture.detectChanges(); // subscribe to process observable with async pipe + + expect(component.startRefreshTimer).toHaveBeenCalled(); + }); + + it('should call refresh method every 5 seconds, until process is completed', fakeAsync(() => { + spyOn(component, 'refresh'); + spyOn(component, 'stopRefreshTimer'); + + process.processStatus = ProcessStatus.COMPLETED; + // set findbyId to return a completed process + (processService.findById as jasmine.Spy).and.returnValue(observableOf(createSuccessfulRemoteDataObject(process))); + + component.ngOnInit(); + fixture.detectChanges(); // subscribe to process observable with async pipe + + expect(component.refresh).not.toHaveBeenCalled(); + + expect(component.refreshCounter$.value).toBe(0); + + tick(1001); // 1 second + 1 ms by the setTimeout + expect(component.refreshCounter$.value).toBe(5); // 5 - 0 + + tick(2001); // 2 seconds + 1 ms by the setTimeout + expect(component.refreshCounter$.value).toBe(3); // 5 - 2 + + tick(2001); // 2 seconds + 1 ms by the setTimeout + expect(component.refreshCounter$.value).toBe(1); // 3 - 2 + + tick(1001); // 1 second + 1 ms by the setTimeout + expect(component.refreshCounter$.value).toBe(0); // 1 - 1 + + tick(1000); // 1 second + + expect(component.refresh).toHaveBeenCalledTimes(1); + expect(component.stopRefreshTimer).toHaveBeenCalled(); + + expect(component.refreshCounter$.value).toBe(0); + + tick(1001); // 1 second + 1 ms by the setTimeout + // startRefreshTimer not called again + expect(component.refreshCounter$.value).toBe(0); + + discardPeriodicTasks(); // discard any periodic tasks that have not yet executed + })); + + it('should show if refreshCounter is different from 0', () => { + component.refreshCounter$.next(1); + fixture.detectChanges(); + + const refreshCounter = queryRefreshCounter(); + expect(refreshCounter).not.toBeNull(); + }); + + }); + + }); }); From b1aa2f3550b5ab2aa01736a99d3eb307f5e7b004 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 6 Jun 2023 16:57:17 +0200 Subject: [PATCH 77/81] [DURACOM-151] reverted setStaleByUUID method as it was --- .../eperson-form/eperson-form.component.ts | 1 - src/app/core/data/request.service.spec.ts | 7 +++++-- src/app/core/data/request.service.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index 1fd0e05ccb..d009d56058 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -495,7 +495,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy { ).subscribe(({ restResponse, eperson }: { restResponse: RemoteData | null, eperson: EPerson }) => { if (restResponse?.hasSucceeded) { this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: this.dsoNameService.getName(eperson) })); - this.submitForm.emit(); } else { this.notificationsService.error(`Error occurred when trying to delete EPerson with id: ${eperson?.id} with code: ${restResponse?.statusCode} and message: ${restResponse?.errorMessage}`); } diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index 8509f60eb7..108a588881 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -627,8 +627,11 @@ describe('RequestService', () => { it('should return an Observable that emits true as soon as the request is stale', fakeAsync(() => { dispatchSpy.and.callFake(() => { /* empty */ }); // don't actually set as stale - getByUUIDSpy.and.returnValue(cold('-----(a|)', { // but fake the state in the cache - a: { state: RequestEntryState.SuccessStale }, + getByUUIDSpy.and.returnValue(cold('a-b--c--d-', { // but fake the state in the cache + a: { state: RequestEntryState.ResponsePending }, + b: { state: RequestEntryState.Success }, + c: { state: RequestEntryState.SuccessStale }, + d: { state: RequestEntryState.Error }, })); const done$ = service.setStaleByUUID('something'); diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 78f53ffe3a..1f6680203e 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -328,9 +328,9 @@ export class RequestService { this.store.dispatch(new RequestStaleAction(uuid)); return this.getByUUID(uuid).pipe( - take(1), map((request: RequestEntry) => isStale(request.state)), filter((stale: boolean) => stale), + take(1), ); } From 755451191c0dbb571f0af1ad4210518724702325 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 6 Jun 2023 18:27:07 +0200 Subject: [PATCH 78/81] 100553: Updated form validation errors from metadata schema and field form --- .../metadata-schema-form.component.ts | 10 ++++++++-- .../metadata-field-form.component.ts | 8 ++++++-- src/assets/i18n/en.json5 | 13 ++++++++++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts index b7c16bc83f..3be4d80fa9 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts @@ -87,11 +87,13 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { name: 'name', validators: { required: null, - pattern: '^[^. ,_]{1,32}$', + pattern: '^[^. ,]*$', + maxLength: 32, }, required: true, errorMessages: { - pattern: 'error.validation.metadata.namespace.invalid-pattern', + pattern: 'error.validation.metadata.name.invalid-pattern', + maxLength: 'error.validation.metadata.name.max-length', }, }); this.namespace = new DynamicInputModel({ @@ -100,8 +102,12 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { name: 'namespace', validators: { required: null, + maxLength: 256, }, required: true, + errorMessages: { + maxLength: 'error.validation.metadata.namespace.max-length', + }, }); this.formModel = [ new DynamicFormGroupModel( diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts index 55950a8773..df8373e8d2 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts @@ -109,11 +109,13 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { name: 'element', validators: { required: null, - pattern: '^[^.]*$', + pattern: '^[^. ,]*$', + maxLength: 64, }, required: true, errorMessages: { pattern: 'error.validation.metadata.element.invalid-pattern', + maxLength: 'error.validation.metadata.element.max-length', }, }); this.qualifier = new DynamicInputModel({ @@ -121,11 +123,13 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { label: qualifier, name: 'qualifier', validators: { - pattern: '^[^.]*$', + pattern: '^[^. ,]*$', + maxLength: 64, }, required: false, errorMessages: { pattern: 'error.validation.metadata.qualifier.invalid-pattern', + maxLength: 'error.validation.metadata.qualifier.max-length', }, }); this.scopeNote = new DynamicInputModel({ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c0edb83a3b..55c788ffac 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1478,12 +1478,19 @@ "error.validation.groupExists": "This group already exists", - "error.validation.metadata.namespace.invalid-pattern": "This field cannot contain dots, please use the Element & Qualifier fields instead", + "error.validation.metadata.name.invalid-pattern": "This field cannot contain dots, commas or spaces. Please use the Element & Qualifier fields instead", - "error.validation.metadata.element.invalid-pattern": "This field cannot contain dots, please use the Qualifier field instead", + "error.validation.metadata.name.max-length": "This field may not contain more than 32 characters", - "error.validation.metadata.qualifier.invalid-pattern": "This field cannot contain dots", + "error.validation.metadata.namespace.max-length": "This field may not contain more than 256 characters", + "error.validation.metadata.element.invalid-pattern": "This field cannot contain dots, commas or spaces. Please use the Qualifier field instead", + + "error.validation.metadata.element.max-length": "This field may not contain more than 64 characters", + + "error.validation.metadata.qualifier.invalid-pattern": "This field cannot contain dots, commas or spaces", + + "error.validation.metadata.qualifier.max-length": "This field may not contain more than 64 characters", "feed.description": "Syndication feed", From 77a8fde646d22e060d7a44b48e1fa2c0c1875fb9 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 7 Jun 2023 09:36:01 +0200 Subject: [PATCH 79/81] 101623: Fix Browse By Vocabulary on comcol page --- .../browse-by-taxonomy-page.component.html | 6 +++++- src/assets/i18n/en.json5 | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html index 87c7937b1b..0ae3da6847 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html @@ -6,5 +6,9 @@ (deselect)="onDeselect($event)">
- {{ 'browse.taxonomy.button' | translate }} + + {{ 'browse.taxonomy.button' | translate }}
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0958fc5d29..33752cbb8a 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -701,6 +701,8 @@ "browse.comcol.by.subject": "By Subject", + "browse.comcol.by.srsc": "By Subject Category", + "browse.comcol.by.title": "By Title", "browse.comcol.head": "Browse", From 02bb7db1190c54be1f2a00608c624fdf67804f23 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 8 Jun 2023 13:15:02 +0200 Subject: [PATCH 80/81] [CST-5729] Fix issue with undefined link type --- .../bitstream-download-page.component.ts | 1 + .../item-page/simple/item-page.component.spec.ts | 13 +++++++++---- src/app/item-page/simple/item-page.component.ts | 15 ++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts index 4d05511ca0..cf8d8e7767 100644 --- a/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts +++ b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts @@ -107,6 +107,7 @@ export class BitstreamDownloadPageComponent implements OnInit { let links = ''; signpostingLinks.forEach((link: SignpostingLink) => { + links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' '); links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}" ; type="${link.type}" `; }); diff --git a/src/app/item-page/simple/item-page.component.spec.ts b/src/app/item-page/simple/item-page.component.spec.ts index dfba4bd235..b3202108f4 100644 --- a/src/app/item-page/simple/item-page.component.spec.ts +++ b/src/app/item-page/simple/item-page.component.spec.ts @@ -49,7 +49,7 @@ const mocklink = { const mocklink2 = { href: 'http://test2.org', rel: 'rel2', - type: 'type2' + type: undefined }; const mockSignpostingLinks: SignpostingLink[] = [mocklink, mocklink2]; @@ -176,13 +176,18 @@ describe('ItemPageComponent', () => { // Check if linkHeadService.addTag() was called with the correct arguments expect(linkHeadService.addTag).toHaveBeenCalledTimes(mockSignpostingLinks.length); - expect(linkHeadService.addTag).toHaveBeenCalledWith(mockSignpostingLinks[0] as LinkDefinition); - expect(linkHeadService.addTag).toHaveBeenCalledWith(mockSignpostingLinks[1] as LinkDefinition); + let expected: LinkDefinition = mockSignpostingLinks[0] as LinkDefinition; + expect(linkHeadService.addTag).toHaveBeenCalledWith(expected); + expected = { + href: 'http://test2.org', + rel: 'rel2' + }; + expect(linkHeadService.addTag).toHaveBeenCalledWith(expected); }); it('should set Link header on the server', () => { - expect(serverResponseService.setHeader).toHaveBeenCalledWith('Link', ' ; rel="rel1" ; type="type1" , ; rel="rel2" ; type="type2" '); + expect(serverResponseService.setHeader).toHaveBeenCalledWith('Link', ' ; rel="rel1" ; type="type1" , ; rel="rel2" '); }); }); diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index a11cb22883..b9be6bebfb 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -20,7 +20,7 @@ import { ServerResponseService } from '../../core/services/server-response.servi import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { SignpostingLink } from '../../core/data/signposting-links.model'; import { isNotEmpty } from '../../shared/empty.util'; -import { LinkHeadService } from '../../core/services/link-head.service'; +import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service'; /** * This component renders a simple item page. @@ -111,12 +111,17 @@ export class ItemPageComponent implements OnInit, OnDestroy { this.signpostingLinks = signpostingLinks; signpostingLinks.forEach((link: SignpostingLink) => { - links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}" ; type="${link.type}" `; - this.linkHeadService.addTag({ + links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' '); + let tag: LinkDefinition = { href: link.href, - type: link.type, rel: link.rel - }); + }; + if (isNotEmpty(link.type)) { + tag = Object.assign(tag, { + type: link.type + }); + } + this.linkHeadService.addTag(tag); }); if (isPlatformServer(this.platformId)) { From 96903d89dedfc6191fc71b5b1b81253783459d74 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 9 Jun 2023 19:24:16 +0200 Subject: [PATCH 81/81] [CST-5729] fix signposting proxy url --- server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.ts b/server.ts index 282b1ce29a..d64b80b4ab 100644 --- a/server.ts +++ b/server.ts @@ -182,8 +182,8 @@ export function app() { /** * Proxy the linksets */ - router.use('/links**', createProxyMiddleware({ - target: `${environment.rest.baseUrl}/signposting`, + router.use('/signposting**', createProxyMiddleware({ + target: `${environment.rest.baseUrl}`, pathRewrite: path => path.replace(environment.ui.nameSpace, '/'), changeOrigin: true }));