diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index cfa202fe4a..d3ce471e05 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -170,6 +170,34 @@ + "admin.search.breadcrumbs": "Administrative Search", + + "admin.search.collection.edit": "Edit", + + "admin.search.community.edit": "Edit", + + "admin.search.item.delete": "Delete", + + "admin.search.item.edit": "Edit", + + "admin.search.item.make-private": "Make Private", + + "admin.search.item.make-public": "Make Public", + + "admin.search.item.move": "Move", + + "admin.search.item.private": "Private", + + "admin.search.item.reinstate": "Reinstate", + + "admin.search.item.withdraw": "Withdraw", + + "admin.search.item.withdrawn": "Withdrawn", + + "admin.search.title": "Administrative Search", + + + "auth.errors.invalid-user": "Invalid email address or password.", "auth.messages.expired": "Your session has expired. Please log in again.", @@ -1117,6 +1145,10 @@ + "menu.section.admin_search": "Admin Search", + + + "menu.section.browse_community": "This Community", "menu.section.browse_community_by_author": "By Author", @@ -1167,18 +1199,10 @@ - "menu.section.find": "Find", - - "menu.section.find_items": "Items", - - "menu.section.find_private_items": "Private Items", - - "menu.section.find_withdrawn_items": "Withdrawn Items", - - - "menu.section.icon.access_control": "Access Control menu section", + "menu.section.icon.admin_search": "Admin search menu section", + "menu.section.icon.control_panel": "Control Panel menu section", "menu.section.icon.curation_task": "Curation Task menu section", @@ -1498,6 +1522,8 @@ "search.filters.applied.f.dateSubmitted": "Date submitted", + "search.filters.applied.f.discoverable": "Private", + "search.filters.applied.f.entityType": "Item Type", "search.filters.applied.f.has_content_in_original_bundle": "Has files", @@ -1509,10 +1535,15 @@ "search.filters.applied.f.subject": "Subject", "search.filters.applied.f.submitter": "Submitter", + "search.filters.applied.f.jobTitle": "Job Title", + "search.filters.applied.f.birthDate.max": "End birth date", + "search.filters.applied.f.birthDate.min": "Start birth date", + "search.filters.applied.f.withdrawn": "Withdrawn", + "search.filters.filter.author.head": "Author", @@ -1549,6 +1580,10 @@ "search.filters.filter.dateSubmitted.placeholder": "Date submitted", + "search.filters.filter.discoverable.head": "Private", + + "search.filters.filter.withdrawn.head": "Withdrawn", + "search.filters.filter.entityType.head": "Item Type", "search.filters.filter.entityType.placeholder": "Item Type", @@ -1605,6 +1640,25 @@ + "search.filters.entityType.JournalIssue": "Journal Issue", + + "search.filters.entityType.JournalVolume": "Journal Volume", + + "search.filters.entityType.OrgUnit": "Organizational Unit", + + "search.filters.has_content_in_original_bundle.true": "Yes", + + "search.filters.has_content_in_original_bundle.false": "No", + + "search.filters.discoverable.true": "No", + + "search.filters.discoverable.false": "Yes", + + "search.filters.withdrawn.true": "Yes", + + "search.filters.withdrawn.false": "No", + + "search.filters.head": "Filters", "search.filters.reset": "Reset filters", @@ -1984,6 +2038,8 @@ "title": "DSpace", + "administrativeView.search.results.head": "Administrative Search", + "uploader.browse": "browse", diff --git a/resources/i18n/nl.json5 b/resources/i18n/nl.json5 index 344a36e2e8..871efcbd04 100644 --- a/resources/i18n/nl.json5 +++ b/resources/i18n/nl.json5 @@ -1248,7 +1248,7 @@ // "journalissue.listelement.badge": "Journal Issue", - "journalissue.listelement.badge": "Tijdschrift aflevering", + "journalissue.listelement.badge": "Tijdschriftaflevering", // "journalissue.page.description": "Description", "journalissue.page.description": "Beschrijving", diff --git a/src/app/+admin/admin-routing.module.ts b/src/app/+admin/admin-routing.module.ts index 2003ecf124..373551d3c0 100644 --- a/src/app/+admin/admin-routing.module.ts +++ b/src/app/+admin/admin-routing.module.ts @@ -2,6 +2,8 @@ import { RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; import { URLCombiner } from '../core/url-combiner/url-combiner'; import { getAdminModulePath } from '../app-routing.module'; +import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component'; +import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; const REGISTRIES_MODULE_PATH = 'registries'; @@ -15,7 +17,13 @@ export function getRegistriesModulePath() { { path: REGISTRIES_MODULE_PATH, loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule' - } + }, + { + path: 'search', + resolve: { breadcrumb: I18nBreadcrumbResolver }, + component: AdminSearchPageComponent, + data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' } + }, ]) ] }) diff --git a/src/app/+admin/admin-search-page/admin-search-page.component.html b/src/app/+admin/admin-search-page/admin-search-page.component.html new file mode 100644 index 0000000000..69ff132fe3 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-page.component.html @@ -0,0 +1 @@ + diff --git a/src/app/+admin/admin-search-page/admin-search-page.component.scss b/src/app/+admin/admin-search-page/admin-search-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+admin/admin-search-page/admin-search-page.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-page.component.spec.ts new file mode 100644 index 0000000000..05546d180b --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-page.component.spec.ts @@ -0,0 +1,27 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminSearchPageComponent } from './admin-search-page.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +describe('AdminSearchPageComponent', () => { + let component: AdminSearchPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AdminSearchPageComponent ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminSearchPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/+admin/admin-search-page/admin-search-page.component.ts b/src/app/+admin/admin-search-page/admin-search-page.component.ts new file mode 100644 index 0000000000..cbbc65a489 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-page.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; +import { Context } from '../../core/shared/context.model'; + +@Component({ + selector: 'ds-admin-search-page', + templateUrl: './admin-search-page.component.html', + styleUrls: ['./admin-search-page.component.scss'] +}) + +/** + * Component that represents a search page for administrators + */ +export class AdminSearchPageComponent { + /** + * The context of this page + */ + context: Context = Context.AdminSearch; +} diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.html b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.html new file mode 100644 index 0000000000..8aa75df7dd --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.html @@ -0,0 +1,13 @@ + + + + diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.scss b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts new file mode 100644 index 0000000000..1dcd978095 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts @@ -0,0 +1,66 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; +import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service'; +import { SharedModule } from '../../../../../shared/shared.module'; +import { CollectionAdminSearchResultGridElementComponent } from './collection-admin-search-result-grid-element.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model'; +import { Collection } from '../../../../../core/shared/collection.model'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { getCollectionEditPath } from '../../../../../+collection-page/collection-page-routing.module'; + +describe('CollectionAdminSearchResultGridElementComponent', () => { + let component: CollectionAdminSearchResultGridElementComponent; + let fixture: ComponentFixture; + let id; + let searchResult; + + function init() { + id = '780b2588-bda5-4112-a1cd-0b15000a5339'; + searchResult = new CollectionSearchResult(); + searchResult.indexableObject = new Collection(); + searchResult.indexableObject.uuid = id; + } + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]), + SharedModule + ], + declarations: [CollectionAdminSearchResultGridElementComponent], + providers: [ + { provide: TruncatableService, useValue: mockTruncatableService }, + { provide: BitstreamDataService, useValue: {} }, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionAdminSearchResultGridElementComponent); + component = fixture.componentInstance; + component.object = searchResult; + component.linkTypes = CollectionElementLinkType; + component.index = 0; + component.viewModes = ViewMode; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render an edit button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.edit-link')); + const link = a.nativeElement.href; + expect(link).toContain(getCollectionEditPath(id)); + }) +}); diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts new file mode 100644 index 0000000000..5e784165ab --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { Context } from '../../../../../core/shared/context.model'; +import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model'; +import { Collection } from '../../../../../core/shared/collection.model'; +import { getCollectionEditPath } from '../../../../../+collection-page/collection-page-routing.module'; +import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component'; + +@listableObjectComponent(CollectionSearchResult, ViewMode.GridElement, Context.AdminSearch) +@Component({ + selector: 'ds-collection-admin-search-result-list-element', + styleUrls: ['./collection-admin-search-result-grid-element.component.scss'], + templateUrl: './collection-admin-search-result-grid-element.component.html' +}) +/** + * The component for displaying a list element for a collection search result on the admin search page + */ +export class CollectionAdminSearchResultGridElementComponent extends SearchResultGridElementComponent { + editPath: string; + + ngOnInit() { + super.ngOnInit(); + this.editPath = getCollectionEditPath(this.dso.uuid); + } +} diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.html b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.html new file mode 100644 index 0000000000..4f6d80c43b --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.html @@ -0,0 +1,13 @@ + + + + diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.scss b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts new file mode 100644 index 0000000000..99d33f841a --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts @@ -0,0 +1,70 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateModule } from '@ngx-translate/core'; +import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; +import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service'; +import { SharedModule } from '../../../../../shared/shared.module'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { CommunityAdminSearchResultGridElementComponent } from './community-admin-search-result-grid-element.component'; +import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; +import { getCommunityEditPath } from '../../../../../+community-page/community-page-routing.module'; +import { Community } from '../../../../../core/shared/community.model'; +import { CommunityAdminSearchResultListElementComponent } from '../../admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component'; + +describe('CommunityAdminSearchResultGridElementComponent', () => { + let component: CommunityAdminSearchResultGridElementComponent; + let fixture: ComponentFixture; + let id; + let searchResult; + + function init() { + id = '780b2588-bda5-4112-a1cd-0b15000a5339'; + searchResult = new CommunitySearchResult(); + searchResult.indexableObject = new Community(); + searchResult.indexableObject.uuid = id; + } + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]), + SharedModule + ], + declarations: [CommunityAdminSearchResultGridElementComponent], + providers: [ + { provide: TruncatableService, useValue: mockTruncatableService }, + { provide: BitstreamDataService, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityAdminSearchResultGridElementComponent); + component = fixture.componentInstance; + component.object = searchResult; + component.linkTypes = CollectionElementLinkType; + component.index = 0; + component.viewModes = ViewMode; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render an edit button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.edit-link')); + const link = a.nativeElement.href; + expect(link).toContain(getCommunityEditPath(id)); + }) +}); diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts new file mode 100644 index 0000000000..8df12e703f --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { Context } from '../../../../../core/shared/context.model'; +import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; +import { Community } from '../../../../../core/shared/community.model'; +import { getCommunityEditPath } from '../../../../../+community-page/community-page-routing.module'; +import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component'; + +@listableObjectComponent(CommunitySearchResult, ViewMode.GridElement, Context.AdminSearch) +@Component({ + selector: 'ds-community-admin-search-result-grid-element', + styleUrls: ['./community-admin-search-result-grid-element.component.scss'], + templateUrl: './community-admin-search-result-grid-element.component.html' +}) +/** + * The component for displaying a list element for a community search result on the admin search page + */ +export class CommunityAdminSearchResultGridElementComponent extends SearchResultGridElementComponent { + editPath: string; + + ngOnInit() { + super.ngOnInit(); + this.editPath = getCommunityEditPath(this.dso.uuid); + } +} diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html new file mode 100644 index 0000000000..c571c3d663 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html @@ -0,0 +1,15 @@ + + +
+
+ {{ "admin.search.item.private" | translate }} +
+
+ {{ "admin.search.item.withdrawn" | translate }} +
+
+
    +
  • + +
  • +
diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.scss b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts new file mode 100644 index 0000000000..d940781f79 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts @@ -0,0 +1,121 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateModule } from '@ngx-translate/core'; +import { Observable } from 'rxjs/internal/Observable'; +import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; +import { RemoteData } from '../../../../../core/data/remote-data'; +import { Bitstream } from '../../../../../core/shared/bitstream.model'; +import { Item } from '../../../../../core/shared/item.model'; +import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service'; +import { SharedModule } from '../../../../../shared/shared.module'; +import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { ItemAdminSearchResultGridElementComponent } from './item-admin-search-result-grid-element.component'; + +describe('ItemAdminSearchResultGridElementComponent', () => { + let component: ItemAdminSearchResultGridElementComponent; + let fixture: ComponentFixture; + let id; + let searchResult; + + const mockBitstreamDataService = { + getThumbnailFor(item: Item): Observable> { + return createSuccessfulRemoteDataObject$(new Bitstream()); + } + }; + + function init() { + id = '780b2588-bda5-4112-a1cd-0b15000a5339'; + searchResult = new ItemSearchResult(); + searchResult.indexableObject = new Item(); + searchResult.indexableObject.uuid = id; + } + + beforeEach(async(() => { + init(); + TestBed.configureTestingModule( + { + declarations: [ItemAdminSearchResultGridElementComponent], + imports: [ + NoopAnimationsModule, + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]), + SharedModule + ], + providers: [ + { provide: TruncatableService, useValue: mockTruncatableService }, + { provide: BitstreamDataService, useValue: mockBitstreamDataService }, + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemAdminSearchResultGridElementComponent); + component = fixture.componentInstance; + component.object = searchResult; + component.linkTypes = CollectionElementLinkType; + component.index = 0; + component.viewModes = ViewMode; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('when the item is not withdrawn', () => { + beforeEach(() => { + component.dso.isWithdrawn = false; + fixture.detectChanges(); + }); + + it('should not show the withdrawn badge', () => { + const badge = fixture.debugElement.query(By.css('div.withdrawn-badge')); + expect(badge).toBeNull(); + }); + }); + + describe('when the item is withdrawn', () => { + beforeEach(() => { + component.dso.isWithdrawn = true; + fixture.detectChanges(); + }); + + it('should show the withdrawn badge', () => { + const badge = fixture.debugElement.query(By.css('div.withdrawn-badge')); + expect(badge).not.toBeNull(); + }); + }); + + describe('when the item is not private', () => { + beforeEach(() => { + component.dso.isDiscoverable = true; + fixture.detectChanges(); + }); + it('should not show the private badge', () => { + const badge = fixture.debugElement.query(By.css('div.private-badge')); + expect(badge).toBeNull(); + }); + }); + + describe('when the item is private', () => { + beforeEach(() => { + component.dso.isDiscoverable = false; + fixture.detectChanges(); + }); + + it('should show the private badge', () => { + const badge = fixture.debugElement.query(By.css('div.private-badge')); + expect(badge).not.toBeNull(); + }); + }) +}); diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts new file mode 100644 index 0000000000..04558f6320 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts @@ -0,0 +1,75 @@ +import { Component, ComponentFactoryResolver, ElementRef, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { Item } from '../../../../../core/shared/item.model'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { getListableObjectComponent, listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { Context } from '../../../../../core/shared/context.model'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { getItemEditPath } from '../../../../../+item-page/item-page-routing.module'; +import { URLCombiner } from '../../../../../core/url-combiner/url-combiner'; +import { + ITEM_EDIT_DELETE_PATH, + ITEM_EDIT_MOVE_PATH, + ITEM_EDIT_PRIVATE_PATH, + ITEM_EDIT_PUBLIC_PATH, + ITEM_EDIT_REINSTATE_PATH, + ITEM_EDIT_WITHDRAW_PATH +} from '../../../../../+item-page/edit-item-page/edit-item-page.routing.module'; +import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; +import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; +import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; + +@listableObjectComponent(ItemSearchResult, ViewMode.GridElement, Context.AdminSearch) +@Component({ + selector: 'ds-item-admin-search-result-grid-element', + styleUrls: ['./item-admin-search-result-grid-element.component.scss'], + templateUrl: './item-admin-search-result-grid-element.component.html' +}) +/** + * The component for displaying a list element for an item search result on the admin search page + */ +export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { + @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; + @ViewChild('badges', { static: true }) badges: ElementRef; + @ViewChild('buttons', { static: true }) buttons: ElementRef; + + constructor(protected truncatableService: TruncatableService, + protected bitstreamDataService: BitstreamDataService, + private componentFactoryResolver: ComponentFactoryResolver + ) { + super(truncatableService, bitstreamDataService); + } + + /** + * Setup the dynamic child component + */ + ngOnInit(): void { + super.ngOnInit(); + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); + + const viewContainerRef = this.listableObjectDirective.viewContainerRef; + viewContainerRef.clear(); + + const componentRef = viewContainerRef.createComponent( + componentFactory, + 0, + undefined, + [ + [this.badges.nativeElement], + [this.buttons.nativeElement] + ]); + (componentRef.instance as any).object = this.object; + (componentRef.instance as any).index = this.index; + (componentRef.instance as any).linkType = this.linkType; + (componentRef.instance as any).listID = this.listID; + } + + /** + * Fetch the component depending on the item's relationship type, view mode and context + * @returns {GenericConstructor} + */ + private getComponent(): GenericConstructor { + return getListableObjectComponent(this.object.getRenderTypes(), ViewMode.GridElement, undefined) + } +} diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.html b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.html new file mode 100644 index 0000000000..e51e207bbe --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.html @@ -0,0 +1,9 @@ + + diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.scss b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts new file mode 100644 index 0000000000..259d1d64aa --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts @@ -0,0 +1,60 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CollectionAdminSearchResultListElementComponent } from './collection-admin-search-result-list-element.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model'; +import { Collection } from '../../../../../core/shared/collection.model'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { getCollectionEditPath } from '../../../../../+collection-page/collection-page-routing.module'; + +describe('CollectionAdminSearchResultListElementComponent', () => { + let component: CollectionAdminSearchResultListElementComponent; + let fixture: ComponentFixture; + let id; + let searchResult; + + function init() { + id = '780b2588-bda5-4112-a1cd-0b15000a5339'; + searchResult = new CollectionSearchResult(); + searchResult.indexableObject = new Collection(); + searchResult.indexableObject.uuid = id; + } + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]) + ], + declarations: [CollectionAdminSearchResultListElementComponent], + providers: [{ provide: TruncatableService, useValue: {} }], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionAdminSearchResultListElementComponent); + component = fixture.componentInstance; + component.object = searchResult; + component.linkTypes = CollectionElementLinkType; + component.index = 0; + component.viewModes = ViewMode; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render an edit button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a')); + const link = a.nativeElement.href; + expect(link).toContain(getCollectionEditPath(id)); + }) +}); diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts new file mode 100644 index 0000000000..e49f272184 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { Context } from '../../../../../core/shared/context.model'; +import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; +import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model'; +import { Collection } from '../../../../../core/shared/collection.model'; +import { getCollectionEditPath } from '../../../../../+collection-page/collection-page-routing.module'; + +@listableObjectComponent(CollectionSearchResult, ViewMode.ListElement, Context.AdminSearch) +@Component({ + selector: 'ds-collection-admin-search-result-list-element', + styleUrls: ['./collection-admin-search-result-list-element.component.scss'], + templateUrl: './collection-admin-search-result-list-element.component.html' +}) +/** + * The component for displaying a list element for a collection search result on the admin search page + */ +export class CollectionAdminSearchResultListElementComponent extends SearchResultListElementComponent { + editPath: string; + + ngOnInit() { + super.ngOnInit(); + this.editPath = getCollectionEditPath(this.dso.uuid); + } +} diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.html b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.html new file mode 100644 index 0000000000..8938c316f4 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.html @@ -0,0 +1,9 @@ + + diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.scss b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts new file mode 100644 index 0000000000..a7922d7cf5 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts @@ -0,0 +1,60 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { CommunityAdminSearchResultListElementComponent } from './community-admin-search-result-list-element.component'; +import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; +import { getCommunityEditPath } from '../../../../../+community-page/community-page-routing.module'; +import { Community } from '../../../../../core/shared/community.model'; + +describe('CommunityAdminSearchResultListElementComponent', () => { + let component: CommunityAdminSearchResultListElementComponent; + let fixture: ComponentFixture; + let id; + let searchResult; + + function init() { + id = '780b2588-bda5-4112-a1cd-0b15000a5339'; + searchResult = new CommunitySearchResult(); + searchResult.indexableObject = new Community(); + searchResult.indexableObject.uuid = id; + } + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]) + ], + declarations: [CommunityAdminSearchResultListElementComponent], + providers: [{ provide: TruncatableService, useValue: {} }], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityAdminSearchResultListElementComponent); + component = fixture.componentInstance; + component.object = searchResult; + component.linkTypes = CollectionElementLinkType; + component.index = 0; + component.viewModes = ViewMode; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render an edit button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a')); + const link = a.nativeElement.href; + expect(link).toContain(getCommunityEditPath(id)); + }) +}); diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts new file mode 100644 index 0000000000..71fe4203ef --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { Context } from '../../../../../core/shared/context.model'; +import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; +import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; +import { Community } from '../../../../../core/shared/community.model'; +import { getCommunityEditPath } from '../../../../../+community-page/community-page-routing.module'; + +@listableObjectComponent(CommunitySearchResult, ViewMode.ListElement, Context.AdminSearch) +@Component({ + selector: 'ds-community-admin-search-result-list-element', + styleUrls: ['./community-admin-search-result-list-element.component.scss'], + templateUrl: './community-admin-search-result-list-element.component.html' +}) +/** + * The component for displaying a list element for a community search result on the admin search page + */ +export class CommunityAdminSearchResultListElementComponent extends SearchResultListElementComponent { + editPath: string; + + ngOnInit() { + super.ngOnInit(); + this.editPath = getCommunityEditPath(this.dso.uuid); + } +} diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.html b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.html new file mode 100644 index 0000000000..a74e339e63 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.html @@ -0,0 +1,12 @@ +
+ {{ "admin.search.item.private" | translate }} +
+
+ {{ "admin.search.item.withdrawn" | translate }} +
+ + diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.scss b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts new file mode 100644 index 0000000000..8395bd4f11 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts @@ -0,0 +1,101 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { ItemAdminSearchResultListElementComponent } from './item-admin-search-result-list-element.component'; +import { Item } from '../../../../../core/shared/item.model'; + +describe('ItemAdminSearchResultListElementComponent', () => { + let component: ItemAdminSearchResultListElementComponent; + let fixture: ComponentFixture; + let id; + let searchResult; + + function init() { + id = '780b2588-bda5-4112-a1cd-0b15000a5339'; + searchResult = new ItemSearchResult(); + searchResult.indexableObject = new Item(); + searchResult.indexableObject.uuid = id; + } + + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]) + ], + declarations: [ItemAdminSearchResultListElementComponent], + providers: [{ provide: TruncatableService, useValue: {} }], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemAdminSearchResultListElementComponent); + component = fixture.componentInstance; + component.object = searchResult; + component.linkTypes = CollectionElementLinkType; + component.index = 0; + component.viewModes = ViewMode; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('when the item is not withdrawn', () => { + beforeEach(() => { + component.dso.isWithdrawn = false; + fixture.detectChanges(); + }); + + it('should not show the withdrawn badge', () => { + const badge = fixture.debugElement.query(By.css('div.withdrawn-badge')); + expect(badge).toBeNull(); + }); + }); + + describe('when the item is withdrawn', () => { + beforeEach(() => { + component.dso.isWithdrawn = true; + fixture.detectChanges(); + }); + + it('should show the withdrawn badge', () => { + const badge = fixture.debugElement.query(By.css('div.withdrawn-badge')); + expect(badge).not.toBeNull(); + }); + }); + + describe('when the item is not private', () => { + beforeEach(() => { + component.dso.isDiscoverable = true; + fixture.detectChanges(); + }); + it('should not show the private badge', () => { + const badge = fixture.debugElement.query(By.css('div.private-badge')); + expect(badge).toBeNull(); + }); + }); + + describe('when the item is private', () => { + beforeEach(() => { + component.dso.isDiscoverable = false; + fixture.detectChanges(); + }); + + it('should show the private badge', () => { + const badge = fixture.debugElement.query(By.css('div.private-badge')); + expect(badge).not.toBeNull(); + }); + }) +}); diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.ts new file mode 100644 index 0000000000..b1dea11341 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; +import { Item } from '../../../../../core/shared/item.model'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { Context } from '../../../../../core/shared/context.model'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; + +@listableObjectComponent(ItemSearchResult, ViewMode.ListElement, Context.AdminSearch) +@Component({ + selector: 'ds-item-admin-search-result-list-element', + styleUrls: ['./item-admin-search-result-list-element.component.scss'], + templateUrl: './item-admin-search-result-list-element.component.html' +}) +/** + * The component for displaying a list element for an item search result on the admin search page + */ +export class ItemAdminSearchResultListElementComponent extends SearchResultListElementComponent { + +} diff --git a/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.html b/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.html new file mode 100644 index 0000000000..a4a923e725 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.html @@ -0,0 +1,27 @@ + + {{"admin.search.item.edit" | translate}} + + + + {{"admin.search.item.withdraw" | translate}} + + + + {{"admin.search.item.reinstate" | translate}} + + + + {{"admin.search.item.make-private" | translate}} + + + + {{"admin.search.item.make-public" | translate}} + + + + {{"admin.search.item.delete" | translate}} + + + + {{"admin.search.item.move" | translate}} + diff --git a/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.scss b/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.spec.ts new file mode 100644 index 0000000000..c1aceb477d --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.spec.ts @@ -0,0 +1,144 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ItemAdminSearchResultActionsComponent } from './item-admin-search-result-actions.component'; +import { Item } from '../../../core/shared/item.model'; +import { + ITEM_EDIT_DELETE_PATH, + ITEM_EDIT_MOVE_PATH, + ITEM_EDIT_PRIVATE_PATH, + ITEM_EDIT_PUBLIC_PATH, + ITEM_EDIT_REINSTATE_PATH, + ITEM_EDIT_WITHDRAW_PATH +} from '../../../+item-page/edit-item-page/edit-item-page.routing.module'; +import { getItemEditPath } from '../../../+item-page/item-page-routing.module'; +import { URLCombiner } from '../../../core/url-combiner/url-combiner'; + +describe('ItemAdminSearchResultActionsComponent', () => { + let component: ItemAdminSearchResultActionsComponent; + let fixture: ComponentFixture; + let id; + let item; + + function init() { + id = '780b2588-bda5-4112-a1cd-0b15000a5339'; + item = new Item(); + item.uuid = id; + } + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]) + ], + declarations: [ItemAdminSearchResultActionsComponent], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemAdminSearchResultActionsComponent); + component = fixture.componentInstance; + component.item = item; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render an edit button with the correct link', () => { + const button = fixture.debugElement.query(By.css('a.edit-link')); + const link = button.nativeElement.href; + expect(link).toContain(getItemEditPath(id)); + }); + + it('should render a delete button with the correct link', () => { + const button = fixture.debugElement.query(By.css('a.delete-link')); + const link = button.nativeElement.href; + expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_DELETE_PATH).toString()); + }); + + it('should render a move button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.move-link')); + const link = a.nativeElement.href; + expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_MOVE_PATH).toString()); + }); + + describe('when the item is not withdrawn', () => { + beforeEach(() => { + component.item.isWithdrawn = false; + fixture.detectChanges(); + }); + + it('should render a withdraw button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.withdraw-link')); + const link = a.nativeElement.href; + expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_WITHDRAW_PATH).toString()); + }); + + it('should not render a reinstate button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.reinstate-link')); + expect(a).toBeNull(); + }); + }); + + describe('when the item is withdrawn', () => { + beforeEach(() => { + component.item.isWithdrawn = true; + fixture.detectChanges(); + }); + + it('should not render a withdraw button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.withdraw-link')); + expect(a).toBeNull(); + }); + + it('should render a reinstate button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.reinstate-link')); + const link = a.nativeElement.href; + expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_REINSTATE_PATH).toString()); + }); + }); + + describe('when the item is not private', () => { + beforeEach(() => { + component.item.isDiscoverable = true; + fixture.detectChanges(); + }); + + it('should render a make private button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.private-link')); + const link = a.nativeElement.href; + expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_PRIVATE_PATH).toString()); + }); + + it('should not render a make public button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.public-link')); + expect(a).toBeNull(); + }); + }); + + describe('when the item is private', () => { + beforeEach(() => { + component.item.isDiscoverable = false; + fixture.detectChanges(); + }); + + it('should not render a make private button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.private-link')); + expect(a).toBeNull(); + }); + + it('should render a make private button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.public-link')); + const link = a.nativeElement.href; + expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_PUBLIC_PATH).toString()); + }); + }) +}); diff --git a/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts b/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts new file mode 100644 index 0000000000..40cddc816d --- /dev/null +++ b/src/app/+admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts @@ -0,0 +1,81 @@ +import { Component, Input } from '@angular/core'; +import { Item } from '../../../core/shared/item.model'; +import { getItemEditPath } from '../../../+item-page/item-page-routing.module'; +import { URLCombiner } from '../../../core/url-combiner/url-combiner'; +import { + ITEM_EDIT_DELETE_PATH, + ITEM_EDIT_MOVE_PATH, + ITEM_EDIT_PRIVATE_PATH, + ITEM_EDIT_PUBLIC_PATH, + ITEM_EDIT_REINSTATE_PATH, + ITEM_EDIT_WITHDRAW_PATH +} from '../../../+item-page/edit-item-page/edit-item-page.routing.module'; + +@Component({ + selector: 'ds-item-admin-search-result-actions-element', + styleUrls: ['./item-admin-search-result-actions.component.scss'], + templateUrl: './item-admin-search-result-actions.component.html' +}) +/** + * The component for displaying the actions for a list element for an item search result on the admin search page + */ +export class ItemAdminSearchResultActionsComponent { + /** + * The item to perform the actions on + */ + @Input() public item: Item; + + /** + * Whether or not to use small buttons + */ + @Input() public small: boolean; + + /** + * Returns the path to the edit page of this item + */ + getEditPath(): string { + return getItemEditPath(this.item.uuid) + } + + /** + * Returns the path to the move page of this item + */ + getMovePath(): string { + return new URLCombiner(this.getEditPath(), ITEM_EDIT_MOVE_PATH).toString(); + } + + /** + * Returns the path to the delete page of this item + */ + getDeletePath(): string { + return new URLCombiner(this.getEditPath(), ITEM_EDIT_DELETE_PATH).toString(); + } + + /** + * Returns the path to the withdraw page of this item + */ + getWithdrawPath(): string { + return new URLCombiner(this.getEditPath(), ITEM_EDIT_WITHDRAW_PATH).toString(); + } + + /** + * Returns the path to the reinstate page of this item + */ + getReinstatePath(): string { + return new URLCombiner(this.getEditPath(), ITEM_EDIT_REINSTATE_PATH).toString(); + } + + /** + * Returns the path to the page where the user can make this item private + */ + getPrivatePath(): string { + return new URLCombiner(this.getEditPath(), ITEM_EDIT_PRIVATE_PATH).toString(); + } + + /** + * Returns the path to the page where the user can make this item public + */ + getPublicPath(): string { + return new URLCombiner(this.getEditPath(), ITEM_EDIT_PUBLIC_PATH).toString(); + } +} diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts index 72eb306bf1..9f4fa5f3f6 100644 --- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts @@ -350,53 +350,19 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { link: '' } as LinkMenuItemModel, }, - - /* Search */ + /* Admin Search */ { - id: 'find', + id: 'admin_search', active: false, visible: true, model: { - type: MenuItemType.TEXT, - text: 'menu.section.find' - } as TextMenuItemModel, + type: MenuItemType.LINK, + text: 'menu.section.admin_search', + link: '/admin/search' + } as LinkMenuItemModel, icon: 'search', index: 5 }, - { - id: 'find_items', - parentID: 'find', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.find_items', - link: '/search' - } as LinkMenuItemModel, - }, - { - id: 'find_withdrawn_items', - parentID: 'find', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.find_withdrawn_items', - link: '' - } as LinkMenuItemModel, - }, - { - id: 'find_private_items', - parentID: 'find', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.find_private_items', - link: '' - } as LinkMenuItemModel, - }, - /* Registries */ { id: 'registries', diff --git a/src/app/+admin/admin.module.ts b/src/app/+admin/admin.module.ts index 1495d0fd8c..90fe03cba0 100644 --- a/src/app/+admin/admin.module.ts +++ b/src/app/+admin/admin.module.ts @@ -2,13 +2,42 @@ import { NgModule } from '@angular/core'; import { AdminRegistriesModule } from './admin-registries/admin-registries.module'; import { AdminRoutingModule } from './admin-routing.module'; import { SharedModule } from '../shared/shared.module'; +import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component'; +import { SearchPageModule } from '../+search-page/search-page.module'; +import { ItemAdminSearchResultListElementComponent } from './admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component'; +import { CommunityAdminSearchResultListElementComponent } from './admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component'; +import { CollectionAdminSearchResultListElementComponent } from './admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component'; +import { ItemAdminSearchResultGridElementComponent } from './admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component'; +import { CommunityAdminSearchResultGridElementComponent } from './admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component'; +import { CollectionAdminSearchResultGridElementComponent } from './admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component'; +import { ItemAdminSearchResultActionsComponent } from './admin-search-page/admin-search-results/item-admin-search-result-actions.component'; @NgModule({ imports: [ AdminRegistriesModule, AdminRoutingModule, SharedModule, + SearchPageModule ], + declarations: [ + AdminSearchPageComponent, + ItemAdminSearchResultListElementComponent, + CommunityAdminSearchResultListElementComponent, + CollectionAdminSearchResultListElementComponent, + ItemAdminSearchResultGridElementComponent, + CommunityAdminSearchResultGridElementComponent, + CollectionAdminSearchResultGridElementComponent, + ItemAdminSearchResultActionsComponent + ], + entryComponents: [ + ItemAdminSearchResultListElementComponent, + CommunityAdminSearchResultListElementComponent, + CollectionAdminSearchResultListElementComponent, + ItemAdminSearchResultGridElementComponent, + CommunityAdminSearchResultGridElementComponent, + CollectionAdminSearchResultGridElementComponent, + ItemAdminSearchResultActionsComponent + ] }) export class AdminModule { diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index 61cfda0d9e..b51c74b457 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -40,6 +40,7 @@ const COLLECTION_EDIT_PATH = 'edit'; dso: CollectionPageResolver, breadcrumb: CollectionBreadcrumbResolver }, + runGuardsAndResolvers: 'always', children: [ { path: COLLECTION_EDIT_PATH, diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index 976a4ad0fe..0e2407577c 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -39,6 +39,7 @@ const COMMUNITY_EDIT_PATH = 'edit'; dso: CommunityPageResolver, breadcrumb: CommunityBreadcrumbResolver }, + runGuardsAndResolvers: 'always', children: [ { path: COMMUNITY_EDIT_PATH, diff --git a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts index da667847f7..84cdeec3d0 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts @@ -15,12 +15,12 @@ import { ItemMoveComponent } from './item-move/item-move.component'; import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component'; import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; -const ITEM_EDIT_WITHDRAW_PATH = 'withdraw'; -const ITEM_EDIT_REINSTATE_PATH = 'reinstate'; -const ITEM_EDIT_PRIVATE_PATH = 'private'; -const ITEM_EDIT_PUBLIC_PATH = 'public'; -const ITEM_EDIT_DELETE_PATH = 'delete'; -const ITEM_EDIT_MOVE_PATH = 'move'; +export const ITEM_EDIT_WITHDRAW_PATH = 'withdraw'; +export const ITEM_EDIT_REINSTATE_PATH = 'reinstate'; +export const ITEM_EDIT_PRIVATE_PATH = 'private'; +export const ITEM_EDIT_PUBLIC_PATH = 'public'; +export const ITEM_EDIT_DELETE_PATH = 'delete'; +export const ITEM_EDIT_MOVE_PATH = 'move'; /** * Routing module that handles the routing for the Edit Item page administrator functionality diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index 686c5ff2fc..5caf0e3036 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -30,6 +30,7 @@ const ITEM_EDIT_PATH = 'edit'; item: ItemPageResolver, breadcrumb: ItemBreadcrumbResolver }, + runGuardsAndResolvers: 'always', children: [ { path: '', diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts index befac7f331..e6bb10f293 100644 --- a/src/app/+search-page/configuration-search-page.component.ts +++ b/src/app/+search-page/configuration-search-page.component.ts @@ -1,3 +1,4 @@ +import { switchMap } from 'rxjs/operators'; import { HostWindowService } from '../shared/host-window.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SearchComponent } from './search.component'; diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 456446781c..b69dcaf935 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -14,6 +14,7 @@ import { SearchPageComponent } from './search-page.component'; import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service'; import { SearchFilterService } from '../core/shared/search/search-filter.service'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; +import { TranslateModule } from '@ngx-translate/core'; const components = [ SearchPageComponent, @@ -28,7 +29,7 @@ const components = [ CommonModule, SharedModule, CoreModule.forRoot(), - StatisticsModule.forRoot(), + StatisticsModule.forRoot() ], declarations: components, providers: [ diff --git a/src/app/+search-page/search.component.html b/src/app/+search-page/search.component.html index 36879f33d4..ee96f703f2 100644 --- a/src/app/+search-page/search.component.html +++ b/src/app/+search-page/search.component.html @@ -1,54 +1,55 @@
-
-
- +
+
+ +
-
-
-
- +
+
+ +
+
+
+ + +
+ +
-
-
- - -
- -
-
- - - + + + - - -
-
- + + +
+
+ +
-
diff --git a/src/app/+search-page/search.component.ts b/src/app/+search-page/search.component.ts index b27ebf625f..bbbfdba513 100644 --- a/src/app/+search-page/search.component.ts +++ b/src/app/+search-page/search.component.ts @@ -17,6 +17,7 @@ import { SearchConfigurationService } from '../core/shared/search/search-configu import { SearchService } from '../core/shared/search/search.service'; import { currentPath } from '../shared/utils/route.utils'; import { Router } from '@angular/router'; +import { Context } from '../core/shared/context.model'; @Component({ selector: 'ds-search', @@ -84,6 +85,12 @@ export class SearchComponent implements OnInit { @Input() configuration$: Observable; + /** + * The current context + */ + @Input() + context: Context; + /** * Link to the search page */ diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 4cf5efae41..ddd3cb606d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,7 +3,6 @@ import { RouterModule } from '@angular/router'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; import { AuthenticatedGuard } from './core/auth/authenticated.guard'; -import { Breadcrumb } from './breadcrumbs/breadcrumb/breadcrumb.model'; import { DSpaceObject } from './core/shared/dspace-object.model'; import { Community } from './core/shared/community.model'; import { getCommunityPageRoute } from './+community-page/community-page-routing.module'; @@ -11,7 +10,6 @@ import { Collection } from './core/shared/collection.model'; import { Item } from './core/shared/item.model'; import { getItemPageRoute } from './+item-page/item-page-routing.module'; import { getCollectionPageRoute } from './+collection-page/collection-page-routing.module'; -import { BrowseByDSOBreadcrumbResolver } from './+browse-by/browse-by-dso-breadcrumb.resolver'; const ITEM_MODULE_PATH = 'items'; @@ -69,7 +67,10 @@ export function getDSOPath(dso: DSpaceObject): string { { path: 'workspaceitems', loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule' }, { path: 'workflowitems', loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule' }, { path: '**', pathMatch: 'full', component: PageNotFoundComponent }, - ]) + ], + { + onSameUrlNavigation: 'reload', + }) ], exports: [RouterModule], }) diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index b9fb36e6d8..a23eb27f4a 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -20,7 +20,7 @@ import { ITEM } from '../shared/item.resource-type'; import { configureRequest, filterSuccessfulResponses, - getRequestFromRequestHref, + getRequestFromRequestHref, getRequestFromRequestUUID, getResponseFromEntry } from '../shared/operators'; import { URLCombiner } from '../url-combiner/url-combiner'; @@ -180,14 +180,17 @@ export class ItemDataService extends DataService { const patchOperation = [{ op: 'replace', path: '/withdrawn', value: withdrawn }]; + this.requestService.removeByHrefSubstring('/discover'); + return this.getItemWithdrawEndpoint(itemId).pipe( distinctUntilChanged(), map((endpointURL: string) => new PatchRequest(this.requestService.generateRequestId(), endpointURL, patchOperation) ), configureRequest(this.requestService), - map((request: RestRequest) => request.href), - getRequestFromRequestHref(this.requestService), + map((request: RestRequest) => request.uuid), + getRequestFromRequestUUID(this.requestService), + filter((requestEntry: RequestEntry) => requestEntry.completed), map((requestEntry: RequestEntry) => requestEntry.response) ); } @@ -201,14 +204,17 @@ export class ItemDataService extends DataService { const patchOperation = [{ op: 'replace', path: '/discoverable', value: discoverable }]; + this.requestService.removeByHrefSubstring('/discover'); + return this.getItemDiscoverableEndpoint(itemId).pipe( distinctUntilChanged(), map((endpointURL: string) => new PatchRequest(this.requestService.generateRequestId(), endpointURL, patchOperation) ), configureRequest(this.requestService), - map((request: RestRequest) => request.href), - getRequestFromRequestHref(this.requestService), + map((request: RestRequest) => request.uuid), + getRequestFromRequestUUID(this.requestService), + filter((requestEntry: RequestEntry) => requestEntry.completed), map((requestEntry: RequestEntry) => requestEntry.response) ); } diff --git a/src/app/core/services/route.reducer.ts b/src/app/core/services/route.reducer.ts index 2d5356a5db..5a56fd3d9b 100644 --- a/src/app/core/services/route.reducer.ts +++ b/src/app/core/services/route.reducer.ts @@ -9,6 +9,7 @@ import { SetQueryParameterAction, SetQueryParametersAction } from './route.actions'; +import { isNotEmpty } from '../../shared/empty.util'; /** * Interface to represent the parameter state of a current route in the store @@ -81,7 +82,8 @@ function addParameter(state: RouteState, action: AddParameterAction | AddQueryPa * @param paramType The type of parameter to set: route or query parameter */ function setParameters(state: RouteState, action: SetParametersAction | SetQueryParametersAction, paramType: string): RouteState { - return Object.assign({}, state, { [paramType]: { [action.payload.key]: action.payload.value } }); + const param = isNotEmpty(action.payload) ? { [paramType]: { [action.payload.key]: action.payload.value } } : {}; + return Object.assign({}, state, param); } /** diff --git a/src/app/core/services/route.service.ts b/src/app/core/services/route.service.ts index 661f4acf94..441d058c4c 100644 --- a/src/app/core/services/route.service.ts +++ b/src/app/core/services/route.service.ts @@ -6,7 +6,7 @@ import { combineLatest, Observable } from 'rxjs'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { isEqual } from 'lodash'; -import { AddParameterAction, SetParameterAction, SetParametersAction, SetQueryParametersAction } from './route.actions'; +import { AddParameterAction, SetParameterAction, SetParametersAction, SetQueryParameterAction, SetQueryParametersAction } from './route.actions'; import { CoreState } from '../core.reducers'; import { coreSelector } from '../core.selectors'; import { hasValue } from '../../shared/empty.util'; @@ -194,6 +194,10 @@ export class RouteService { this.store.dispatch(new SetParameterAction(key, value)); } + public setQueryParameter(key, value) { + this.store.dispatch(new SetQueryParameterAction(key, value)); + } + /** * Sets the current route parameters and query parameters in the store */ diff --git a/src/app/core/shared/context.model.ts b/src/app/core/shared/context.model.ts index 7bfd613b65..6bb3d77140 100644 --- a/src/app/core/shared/context.model.ts +++ b/src/app/core/shared/context.model.ts @@ -10,4 +10,5 @@ export enum Context { Workspace = 'workspace', AdminMenu = 'adminMenu', SubmissionModal = 'submissionModal', + AdminSearch = 'adminSearch', } diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html index af339109c6..0f9b5894f9 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html @@ -1,36 +1,43 @@ - -
- -
- - -
-
- +
+ + + +
+ + +
+
+
-
- - -

-
-

- - - -

-

- - - -

-
- View -
-
+
+ + +

+
+

+ + + +

+

+ + + +

+
+ View +
-
+ + +
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html index 2c7f24662a..16e2a8b847 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html @@ -1,36 +1,43 @@ - -
- -
- - -
-
- +
+ + + +
+ + +
+
+
-
- - -

-
-

- - - -

-

- - - -

-
- View -
-
-
- +
+ + +

+
+

+ + + +

+

+ + + +

+
+ View +
+
+ + +
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html index d6b9c4a62e..4902eec71e 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html @@ -1,41 +1,47 @@ - -
- -
- - -
-
- +
+ + + +
+ + +
+
+
-
- - -

-
-

- - {{firstMetadataValue('creativework.editor')}} - +

+ + +

+
+

+ + {{firstMetadataValue('creativework.editor')}} + , - {{firstMetadataValue('creativework.publisher')}} + {{firstMetadataValue('creativework.publisher')}} - -

-

- - - -

-
- View -
-
+ +

+

+ + + +

+
+ View +
-
+ + +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html index 0fb1ec02f8..1f64856583 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html @@ -1,41 +1,49 @@ - -
- -
- - -
-
- +
+ + + +
+ + +
+
+
-
- - -

-
-

- - - -

-

- - {{firstMetadataValue('organization.address.addressCountry')}} - +

+ + +

+
+

+ + + +

+

+ + {{firstMetadataValue('organization.address.addressCountry')}} + , - {{firstMetadataValue('organization.address.addressLocality')}} + {{firstMetadataValue('organization.address.addressLocality')}} - -

-
- View -
-
+ +

+
+ View +
-
+ + +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html index 321ecd4a47..cbe93b2545 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html @@ -1,37 +1,43 @@ - -
- -
- - -
-
- +
+ + + +
+ + +
+
+
-
- - -

-
- -

- - - -

-
- View -
-
+
+ + +

+
+ +

+ + + +

+
+ View +
-
+ + +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html index c39de6bc2a..22182d50be 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html @@ -1,31 +1,37 @@ - -
- -
- - -
-
- +
+ + + +
+ + +
+
+
-
- - -

-
-

- - - -

-
- View -
-
+
+ + +

+
+

+ + + +

+
+ View +
-
+ + +
diff --git a/src/app/shared/mocks/mock-trucatable.service.ts b/src/app/shared/mocks/mock-trucatable.service.ts new file mode 100644 index 0000000000..0acb0b4c76 --- /dev/null +++ b/src/app/shared/mocks/mock-trucatable.service.ts @@ -0,0 +1,19 @@ +import { of as observableOf } from 'rxjs/internal/observable/of'; + +export const mockTruncatableService: any = { + /* tslint:disable:no-empty */ + isCollapsed: (id: string) => { + if (id === '1') { + return observableOf(true) + } else { + return observableOf(false); + } + }, + expand: (id: string) => { + }, + collapse: (id: string) => { + }, + toggle: (id: string) => { + } + /* tslint:enable:no-empty */ +}; diff --git a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts index d27fb331de..3602f45ede 100644 --- a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts +++ b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts @@ -1,6 +1,8 @@ import { Component, Input } from '@angular/core'; import { ListableObject } from '../listable-object.model'; import { CollectionElementLinkType } from '../../collection-element-link.type'; +import { Context } from '../../../../core/shared/context.model'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; @Component({ selector: 'ds-abstract-object-element', @@ -22,8 +24,23 @@ export class AbstractListableElementComponent { */ @Input() listID: string; + /** + * The index of this element + */ + @Input() index: number; + /** * The available link types */ linkTypes = CollectionElementLinkType; + + /** + * The available view modes + */ + viewModes = ViewMode; + + /** + * The available contexts + */ + contexts = Context; } diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html index 0c45316e30..a2933fd0ec 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html @@ -14,4 +14,5 @@ View
+
diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts index c14e3f6df1..de19f5b74a 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts @@ -6,6 +6,7 @@ import { Store } from '@ngrx/store'; import { of as observableOf } from 'rxjs'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; +import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { CommunityDataService } from '../../../../core/data/community-data.service'; import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service'; import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service'; @@ -62,6 +63,7 @@ describe('CollectionSearchResultGridElementComponent', () => { { provide: UUIDService, useValue: {} }, { provide: Store, useValue: {} }, { provide: RemoteDataBuildService, useValue: {} }, + { provide: BitstreamDataService, useValue: {} }, { provide: CommunityDataService, useValue: {} }, { provide: HALEndpointService, useValue: {} }, { provide: NotificationsService, useValue: {} }, diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html index d0a9aa700e..8d5f288498 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html @@ -1,17 +1,18 @@
- - - - - + + + + + -
-

{{dso.name}}

-

{{dso.shortDescription}}

-
- View +
+

{{dso.name}}

+

{{dso.shortDescription}}

+
+ View +
-
+
diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts index 0ea72b52d5..b97c574970 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts @@ -6,6 +6,7 @@ import { Store } from '@ngrx/store'; import { of as observableOf } from 'rxjs'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; +import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { CommunityDataService } from '../../../../core/data/community-data.service'; import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service'; import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service'; @@ -62,6 +63,7 @@ describe('CommunitySearchResultGridElementComponent', () => { { provide: UUIDService, useValue: {} }, { provide: Store, useValue: {} }, { provide: RemoteDataBuildService, useValue: {} }, + { provide: BitstreamDataService, useValue: {} }, { provide: CommunityDataService, useValue: {} }, { provide: HALEndpointService, useValue: {} }, { provide: NotificationsService, useValue: {} }, diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html index 41c16c6eab..ec0b792e34 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html @@ -1,41 +1,44 @@ - -
- -
- - -
-
- -
- - -
-
-
- - -

-
-

- - {{firstMetadataValue('dc.date.issued')}} - , - - - -

-

- - - -

-
- View -
-
-
-
+
+ + + +
+ + +
+
+ +
+ + +
+
+
+ + +

+
+

+ + {{firstMetadataValue('dc.date.issued')}} + , + + + +

+

+ + + +

+
+ View +
+
+
+ +
+ diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.ts index 76618f18f2..c96e73d365 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.ts @@ -7,6 +7,7 @@ import { Item } from '../../../../../core/shared/item.model'; import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model'; @listableObjectComponent('PublicationSearchResult', ViewMode.GridElement) +@listableObjectComponent(ItemSearchResult, ViewMode.GridElement) @Component({ selector: 'ds-publication-search-result-grid-element', styleUrls: ['./publication-search-result-grid-element.component.scss'], diff --git a/src/app/shared/search/paginated-search-options.model.spec.ts b/src/app/shared/search/paginated-search-options.model.spec.ts index 2702c3ff01..1739fd54fb 100644 --- a/src/app/shared/search/paginated-search-options.model.spec.ts +++ b/src/app/shared/search/paginated-search-options.model.spec.ts @@ -27,9 +27,9 @@ describe('PaginatedSearchOptions', () => { 'query=search query&' + 'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' + 'dsoType=ITEM&' + - 'f.test=value,query&' + - 'f.example=another value,query&' + - 'f.example=second value,query' + 'f.test=value&' + + 'f.example=another value&' + + 'f.example=second value' ); }); diff --git a/src/app/shared/search/search-filter.model.ts b/src/app/shared/search/search-filter.model.ts index 9e93bafed8..ee55bec242 100644 --- a/src/app/shared/search/search-filter.model.ts +++ b/src/app/shared/search/search-filter.model.ts @@ -1,7 +1,6 @@ /** * Represents a search filter */ -import { hasValue } from '../empty.util'; export class SearchFilter { key: string; @@ -11,10 +10,6 @@ export class SearchFilter { constructor(key: string, values: string[], operator?: string) { this.key = key; this.values = values; - if (hasValue(operator)) { - this.operator = operator; - } else { - this.operator = 'query'; - } + this.operator = operator; } } diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index 9441081661..cf4876e34f 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -2,7 +2,9 @@ [routerLink]="[searchLink]" [queryParams]="addQueryParams" queryParamsHandling="merge"> - {{filterValue.value}} + + {{ 'search.filters.' + filterConfig.name + '.' + filterValue.value | translate: {default: filterValue.value} }} + {{filterValue.count}} diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html index 5198433207..a27a5d3d86 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html @@ -2,5 +2,7 @@ [routerLink]="[searchLink]" [queryParams]="removeQueryParams" queryParamsHandling="merge"> - {{selectedValue.label}} + + {{ 'search.filters.' + filterConfig.name + '.' + selectedValue.value | translate: {default: selectedValue.value} }} + diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.html b/src/app/shared/search/search-labels/search-label/search-label.component.html index 391efcb763..bffb7f9329 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.html +++ b/src/app/shared/search/search-labels/search-label/search-label.component.html @@ -1,6 +1,6 @@ - {{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(value)}} + {{('search.filters.applied.' + key) | translate}}: {{'search.filters.' + filterName + '.' + value | translate: {default: normalizeFilterValue(value)} }} × - \ No newline at end of file + diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.ts b/src/app/shared/search/search-labels/search-label/search-label.component.ts index 956b5b81de..2203f73a75 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.ts @@ -22,6 +22,11 @@ export class SearchLabelComponent implements OnInit { searchLink: string; removeParameters: Observable; + /** + * The name of the filter without the f. prefix + */ + filterName: string; + /** * Initialize the instance variable */ @@ -33,6 +38,7 @@ export class SearchLabelComponent implements OnInit { ngOnInit(): void { this.searchLink = this.getSearchLink(); this.removeParameters = this.getRemoveParams(); + this.filterName = this.getFilterName(); } /** @@ -74,4 +80,8 @@ export class SearchLabelComponent implements OnInit { const pattern = /,authority*$/g; return value.replace(pattern, ''); } + + private getFilterName(): string { + return this.key.startsWith('f.') ? this.key.substring(2) : this.key; + } } diff --git a/src/app/shared/search/search-options.model.spec.ts b/src/app/shared/search/search-options.model.spec.ts index 3b047b578f..3195ec3660 100644 --- a/src/app/shared/search/search-options.model.spec.ts +++ b/src/app/shared/search/search-options.model.spec.ts @@ -21,9 +21,9 @@ describe('SearchOptions', () => { 'query=search query&' + 'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' + 'dsoType=ITEM&' + - 'f.test=value,query&' + - 'f.example=another value,query&' + - 'f.example=second value,query' + 'f.test=value&' + + 'f.example=another value&' + + 'f.example=second value' ); }); diff --git a/src/app/shared/search/search-options.model.ts b/src/app/shared/search/search-options.model.ts index a8b115abd3..7d5f4dd207 100644 --- a/src/app/shared/search/search-options.model.ts +++ b/src/app/shared/search/search-options.model.ts @@ -50,7 +50,7 @@ export class SearchOptions { if (isNotEmpty(this.filters)) { this.filters.forEach((filter: SearchFilter) => { filter.values.forEach((value) => { - const filterValue = value.includes(',') ? `${value}` : `${value},${filter.operator}`; + const filterValue = value.includes(',') ? `${value}` : value + (filter.operator ? ',' + filter.operator : ''); args.push(`${filter.key}=${filterValue}`) }); }); diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index c6180e3a3b..c229b6c429 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -6,7 +6,7 @@ import { NouisliderModule } from 'ng2-nouislider'; import { NgbDatepickerModule, NgbModule, NgbTimepickerModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateModule } from '@ngx-translate/core'; +import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core'; import { NgxPaginationModule } from 'ngx-pagination'; import { PublicationListElementComponent } from './object-list/item-list-element/item-types/publication/publication-list-element.component'; @@ -177,6 +177,7 @@ import { ImportableListItemControlComponent } from './object-collection/shared/i import { DragDropModule } from '@angular/cdk/drag-drop'; import { ExistingMetadataListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component'; import { SortablejsModule } from 'ngx-sortablejs'; +import { MissingTranslationHelper } from './translate/missing-translation.helper'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -194,7 +195,6 @@ const MODULES = [ NgxPaginationModule, ReactiveFormsModule, RouterModule, - TranslateModule, NouisliderModule, MomentModule, TextMaskModule, @@ -203,7 +203,11 @@ const MODULES = [ ]; const ROOT_MODULES = [ - TooltipModule.forRoot() + TranslateModule.forChild({ + missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationHelper }, + useDefaultLang: true + }), + TooltipModule.forRoot(), ]; const PIPES = [ @@ -339,7 +343,8 @@ const COMPONENTS = [ SelectableListItemControlComponent, ExternalSourceEntryImportModalComponent, ImportableListItemControlComponent, - ExistingMetadataListElementComponent + ExistingMetadataListElementComponent, + PublicationSearchResultListElementComponent, ]; const ENTRY_COMPONENTS = [ @@ -402,7 +407,7 @@ const ENTRY_COMPONENTS = [ DsDynamicLookupRelationSearchTabComponent, DsDynamicLookupRelationSelectionTabComponent, DsDynamicLookupRelationExternalSourceTabComponent, - ExternalSourceEntryImportModalComponent + ExternalSourceEntryImportModalComponent, ]; const SHARED_ITEM_PAGE_COMPONENTS = [ @@ -435,8 +440,8 @@ const DIRECTIVES = [ @NgModule({ imports: [ + ...ROOT_MODULES, ...MODULES, - ...ROOT_MODULES ], declarations: [ ...PIPES, @@ -444,8 +449,7 @@ const DIRECTIVES = [ ...DIRECTIVES, ...ENTRY_COMPONENTS, ...SHARED_ITEM_PAGE_COMPONENTS, - PublicationSearchResultListElementComponent, - ExistingMetadataListElementComponent + ], providers: [ ...PROVIDERS diff --git a/src/app/shared/translate/missing-translation.helper.ts b/src/app/shared/translate/missing-translation.helper.ts new file mode 100644 index 0000000000..71a1dc3620 --- /dev/null +++ b/src/app/shared/translate/missing-translation.helper.ts @@ -0,0 +1,18 @@ +import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core'; + +/** + * Class to handle missing translations for the ngx-translate library + */ +export class MissingTranslationHelper implements MissingTranslationHandler { + /** + * Called when there is not translation for a specific key + * Will return the 'default' parameter of the translate pipe, if there is one available + * @param params + */ + handle(params: MissingTranslationHandlerParams) { + if (params.interpolateParams) { + return (params.interpolateParams as any).default || params.key; + } + return params.key; + } +} diff --git a/src/app/shared/truncatable/truncatable.component.spec.ts b/src/app/shared/truncatable/truncatable.component.spec.ts index d083c27d07..176beb0f15 100644 --- a/src/app/shared/truncatable/truncatable.component.spec.ts +++ b/src/app/shared/truncatable/truncatable.component.spec.ts @@ -1,5 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { of as observableOf } from 'rxjs'; +import { mockTruncatableService } from '../mocks/mock-trucatable.service'; import { TruncatableComponent } from './truncatable.component'; import { TruncatableService } from './truncatable.service'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; @@ -10,29 +11,12 @@ describe('TruncatableComponent', () => { let fixture: ComponentFixture; const identifier = '1234567890'; let truncatableService; - const truncatableServiceStub: any = { - /* tslint:disable:no-empty */ - isCollapsed: (id: string) => { - if (id === '1') { - return observableOf(true) - } else { - return observableOf(false); - } - }, - expand: (id: string) => { - }, - collapse: (id: string) => { - }, - toggle: (id: string) => { - } - /* tslint:enable:no-empty */ - }; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [NoopAnimationsModule], declarations: [TruncatableComponent], providers: [ - { provide: TruncatableService, useValue: truncatableServiceStub }, + { provide: TruncatableService, useValue: mockTruncatableService }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(TruncatableComponent, {