diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 5b3747caf6..e484ce4926 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -826,6 +826,14 @@ "item.page.related-items.view-less": "View less", + "item.page.relationships.isAuthorOfPublication": "Publications", + + "item.page.relationships.isJournalOfPublication": "Publications", + + "item.page.relationships.isOrgUnitOfPerson": "Authors", + + "item.page.relationships.isOrgUnitOfProject": "Research Projects", + "item.page.subject": "Keywords", "item.page.uri": "URI", @@ -1276,6 +1284,8 @@ "project.page.titleprefix": "Research Project: ", + "project.search.results.head": "Project Search Results", + "publication.listelement.badge": "Publication", diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index 28460f567a..940868a0c6 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -26,6 +26,7 @@ import { MetadataRepresentationListComponent } from './simple/metadata-represent import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component'; import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component'; import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.component'; +import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component'; import { StatisticsModule } from '../statistics/statistics.module'; @NgModule({ @@ -55,7 +56,8 @@ import { StatisticsModule } from '../statistics/statistics.module'; ItemComponent, GenericItemPageFieldComponent, MetadataRepresentationListComponent, - RelatedEntitiesSearchComponent + RelatedEntitiesSearchComponent, + TabbedRelatedEntitiesSearchComponent ], exports: [ ItemComponent, @@ -65,7 +67,8 @@ import { StatisticsModule } from '../statistics/statistics.module'; RelatedEntitiesSearchComponent, RelatedItemsComponent, MetadataRepresentationListComponent, - ItemPageTitleFieldComponent + ItemPageTitleFieldComponent, + TabbedRelatedEntitiesSearchComponent ], entryComponents: [ PublicationComponent diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html index c7e1f77264..75f3b7aaad 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html @@ -1,6 +1,7 @@ - - + diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts index 711e1b9d3d..7eeddf8e70 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts @@ -16,7 +16,7 @@ describe('RelatedEntitiesSearchComponent', () => { id: 'id1' }); const mockRelationType = 'publicationsOfAuthor'; - const mockRelationEntityType = 'publication'; + const mockConfiguration = 'publication'; const mockFilter= `f.${mockRelationType}=${mockItem.id}`; const fixedFilterServiceStub = { getFilterByRelation: () => mockFilter @@ -39,7 +39,7 @@ describe('RelatedEntitiesSearchComponent', () => { fixedFilterService = (comp as any).fixedFilterService; comp.relationType = mockRelationType; comp.item = mockItem; - comp.relationEntityType = mockRelationEntityType; + comp.configuration = mockConfiguration; fixture.detectChanges(); }); @@ -49,7 +49,7 @@ describe('RelatedEntitiesSearchComponent', () => { it('should create a configuration$', () => { comp.configuration$.subscribe((configuration) => { - expect(configuration).toEqual(mockRelationEntityType); + expect(configuration).toEqual(mockConfiguration); }) }); diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts index 4c0b127925..8f65cb9858 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts @@ -22,18 +22,16 @@ export class RelatedEntitiesSearchComponent implements OnInit { */ @Input() relationType: string; + /** + * An optional configuration to use for the search options + */ + @Input() configuration: string; + /** * The item to render relationships for */ @Input() item: Item; - /** - * The entity type of the relationship items to be displayed - * e.g. 'publication' - * This determines the title of the search results (if search is enabled) - */ - @Input() relationEntityType: string; - /** * Whether or not the search bar and title should be displayed (defaults to true) * @type {boolean} @@ -56,8 +54,8 @@ export class RelatedEntitiesSearchComponent implements OnInit { if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) { this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id); } - if (isNotEmpty(this.relationEntityType)) { - this.configuration$ = of(this.relationEntityType); + if (isNotEmpty(this.configuration)) { + this.configuration$ = of(this.configuration); } } diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html new file mode 100644 index 0000000000..f9642d2c01 --- /dev/null +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -0,0 +1,22 @@ + + + +
+ + +
+
+
+
+
+ + +
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts new file mode 100644 index 0000000000..2d2e682196 --- /dev/null +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts @@ -0,0 +1,82 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Item } from '../../../../core/shared/item.model'; +import { TranslateModule } from '@ngx-translate/core'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TabbedRelatedEntitiesSearchComponent } from './tabbed-related-entities-search.component'; +import { ActivatedRoute, Router } from '@angular/router'; +import { MockRouter } from '../../../../shared/mocks/mock-router'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { VarDirective } from '../../../../shared/utils/var.directive'; +import { of as observableOf } from 'rxjs'; + +describe('TabbedRelatedEntitiesSearchComponent', () => { + let comp: TabbedRelatedEntitiesSearchComponent; + let fixture: ComponentFixture; + + const mockItem = Object.assign(new Item(), { + id: 'id1' + }); + const mockRelationType = 'publications'; + const relationTypes = [ + { + label: mockRelationType, + filter: mockRelationType + } + ]; + + const router = new MockRouter(); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), NoopAnimationsModule, NgbModule.forRoot()], + declarations: [TabbedRelatedEntitiesSearchComponent, VarDirective], + providers: [ + { + provide: ActivatedRoute, + useValue: { + queryParams: observableOf({ tab: mockRelationType }) + }, + }, + { provide: Router, useValue: router } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TabbedRelatedEntitiesSearchComponent); + comp = fixture.componentInstance; + comp.item = mockItem; + comp.relationTypes = relationTypes; + fixture.detectChanges(); + }); + + it('should initialize the activeTab depending on the current query parameters', () => { + comp.activeTab$.subscribe((activeTab) => { + expect(activeTab).toEqual(mockRelationType); + }); + }); + + describe('onTabChange', () => { + const event = { + currentId: mockRelationType, + nextId: 'nextTab' + }; + + beforeEach(() => { + comp.onTabChange(event); + }); + + it('should call router natigate with the correct arguments', () => { + expect(router.navigate).toHaveBeenCalledWith([], { + relativeTo: (comp as any).route, + queryParams: { + tab: event.nextId + }, + queryParamsHandling: 'merge' + }); + }); + }); + +}); diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts new file mode 100644 index 0000000000..b01eb70720 --- /dev/null +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -0,0 +1,76 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Item } from '../../../../core/shared/item.model'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable } from 'rxjs/internal/Observable'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'ds-tabbed-related-entities-search', + templateUrl: './tabbed-related-entities-search.component.html' +}) +/** + * A component to show related items as search results, split into tabs by relationship-type + * Related items can be facetted, or queried using an + * optional search box. + */ +export class TabbedRelatedEntitiesSearchComponent implements OnInit { + /** + * The types of relationships to fetch items for + * e.g. 'isAuthorOfPublication' + */ + @Input() relationTypes: Array<{ + label: string, + filter: string, + configuration?: string + }>; + + /** + * The item to render relationships for + */ + @Input() item: Item; + + /** + * Whether or not the search bar and title should be displayed (defaults to true) + * @type {boolean} + */ + @Input() searchEnabled = true; + + /** + * The ratio of the sidebar's width compared to the search results (1-12) (defaults to 4) + * @type {number} + */ + @Input() sideBarWidth = 4; + + /** + * The active tab + */ + activeTab$: Observable; + + constructor(private route: ActivatedRoute, + private router: Router) { + } + + /** + * If the url contains a "tab" query parameter, set this tab to be the active tab + */ + ngOnInit(): void { + this.activeTab$ = this.route.queryParams.pipe( + map((params) => params.tab) + ); + } + + /** + * Add a "tab" query parameter to the URL when changing tabs + * @param event + */ + onTabChange(event) { + this.router.navigate([], { + relativeTo: this.route, + queryParams: { + tab: event.nextId + }, + queryParamsHandling: 'merge' + }); + } + +} diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts index 1296f3a203..34446e8371 100644 --- a/src/app/+search-page/configuration-search-page.component.ts +++ b/src/app/+search-page/configuration-search-page.component.ts @@ -35,6 +35,12 @@ export class ConfigurationSearchPageComponent extends SearchComponent implements */ @Input() configuration: string; + /** + * The actual query for the fixed filter. + * If empty, the query will be determined by the route parameter called 'filter' + */ + @Input() fixedFilterQuery: string; + constructor(protected service: SearchService, protected sidebarService: SidebarService, protected windowService: HostWindowService, @@ -64,7 +70,11 @@ export class ConfigurationSearchPageComponent extends SearchComponent implements return this.searchConfigService.paginatedSearchOptions.pipe( map((options: PaginatedSearchOptions) => { const config = this.configuration || options.configuration; - return Object.assign(options, { configuration: config }); + const filter = this.fixedFilterQuery || options.fixedFilter; + return Object.assign(options, { + configuration: config, + fixedFilter: filter + }); }) ); } diff --git a/src/app/+search-page/filtered-search-page.component.spec.ts b/src/app/+search-page/filtered-search-page.component.spec.ts deleted file mode 100644 index cf1668d8bc..0000000000 --- a/src/app/+search-page/filtered-search-page.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FilteredSearchPageComponent } from './filtered-search-page.component'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { configureSearchComponentTestingModule } from './search.component.spec'; -import { SearchConfigurationService } from './search-service/search-configuration.service'; - -describe('FilteredSearchPageComponent', () => { - let comp: FilteredSearchPageComponent; - let fixture: ComponentFixture; - let searchConfigService: SearchConfigurationService; - - beforeEach(async(() => { - configureSearchComponentTestingModule(FilteredSearchPageComponent); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(FilteredSearchPageComponent); - comp = fixture.componentInstance; - searchConfigService = (comp as any).searchConfigService; - fixture.detectChanges(); - }); -}); diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts deleted file mode 100644 index 19e5c09f71..0000000000 --- a/src/app/+search-page/filtered-search-page.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { HostWindowService } from '../shared/host-window.service'; -import { SearchService } from './search-service/search.service'; -import { SidebarService } from '../shared/sidebar/sidebar.service'; -import { SearchComponent } from './search.component'; -import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; -import { pushInOut } from '../shared/animations/push'; -import { SearchConfigurationService } from './search-service/search-configuration.service'; -import { Observable } from 'rxjs'; -import { PaginatedSearchOptions } from './paginated-search-options.model'; -import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; -import { map } from 'rxjs/operators'; -import { RouteService } from '../core/services/route.service'; - -/** - * This component renders a simple item page. - * The route parameter 'id' is used to request the item it represents. - * All fields of the item that should be displayed, are defined in its template. - */ -@Component({ - selector: 'ds-filtered-search-page', - styleUrls: ['./search.component.scss'], - templateUrl: './search.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [pushInOut], - providers: [ - { - provide: SEARCH_CONFIG_SERVICE, - useClass: SearchConfigurationService - } - ] -}) - -export class FilteredSearchPageComponent extends SearchComponent implements OnInit { - /** - * The actual query for the fixed filter. - * If empty, the query will be determined by the route parameter called 'filter' - */ - @Input() fixedFilterQuery: string; - - constructor(protected service: SearchService, - protected sidebarService: SidebarService, - protected windowService: HostWindowService, - @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, - protected routeService: RouteService) { - super(service, sidebarService, windowService, searchConfigService, routeService); - } - - /** - * Listening to changes in the paginated search options - * If something changes, update the search results - * - * Listen to changes in the scope - * If something changes, update the list of scopes for the dropdown - */ - ngOnInit(): void { - super.ngOnInit(); - } - - /** - * Get the current paginated search options after updating the fixed filter using the fixedFilterQuery input - * This is to make sure the fixed filter is included in the paginated search options, as it is not part of any - * query or route parameters - * @returns {Observable} - */ - protected getSearchOptions(): Observable { - return this.searchConfigService.paginatedSearchOptions.pipe( - map((options: PaginatedSearchOptions) => { - const filter = this.fixedFilterQuery || options.fixedFilter; - return Object.assign(options, { fixedFilter: filter }); - }) - ); - } -} diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 8ee81e549b..9eab7d1533 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -32,7 +32,6 @@ import { SearchAuthorityFilterComponent } from './search-filters/search-filter/s import { SearchLabelComponent } from './search-labels/search-label/search-label.component'; import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; -import { FilteredSearchPageComponent } from './filtered-search-page.component'; import { SearchPageComponent } from './search-page.component'; import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service'; import { StatisticsModule } from '../statistics/statistics.module'; @@ -64,7 +63,6 @@ const components = [ SearchFacetRangeOptionComponent, SearchSwitchConfigurationComponent, SearchAuthorityFilterComponent, - FilteredSearchPageComponent, ConfigurationSearchPageComponent, SearchTrackerComponent, ]; diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index ae6c3a8914..e86ab35e0e 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -36,8 +36,11 @@
- - + +
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 4d97868b58..1b23d567f5 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -24,16 +24,6 @@
- - - -
+
+ + +
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index ff675ab057..97a3cf416e 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -53,8 +53,11 @@
- - + +
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index 1b8a283da9..089511804e 100644 --- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -61,7 +61,10 @@

{{"item.page.journal.search.title" | translate}}

- - + + diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html similarity index 79% rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 15529a1bd5..ee78d9c653 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -53,18 +53,6 @@
- - - -
+
+
+ + +
+
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss similarity index 86% rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss index 54651aede0..4a1d2516da 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss'; +@import 'src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss'; :host { > * { diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html index bb5cb1b787..1679f9354d 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -79,7 +79,10 @@

{{"item.page.person.search.title" | translate}}

- - + +