diff --git a/config/config.example.yml b/config/config.example.yml index 500c2c476a..f1e6be76aa 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -369,3 +369,8 @@ vocabularies: - filter: 'subject' vocabulary: 'srsc' enabled: true + +# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. +comcolSelectionSort: + sortField: 'dc.title' + sortDirection: 'ASC' \ No newline at end of file diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html index 2d87f21d26..3a7806a231 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/access-control/epeople-registry/epeople-registry.component.html @@ -8,7 +8,7 @@ @@ -30,7 +30,7 @@
diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts index c0d70fd0b2..4a09913862 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts @@ -1,7 +1,7 @@ import { Router } from '@angular/router'; import { Observable, of as observableOf } from 'rxjs'; import { CommonModule } from '@angular/common'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; @@ -42,6 +42,7 @@ describe('EPeopleRegistryComponent', () => { let paginationService; beforeEach(waitForAsync(() => { + jasmine.getEnv().allowRespy(true); mockEPeople = [EPersonMock, EPersonMock2]; ePersonDataServiceStub = { activeEPerson: null, @@ -98,7 +99,7 @@ describe('EPeopleRegistryComponent', () => { deleteEPerson(ePerson: EPerson): Observable { this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => { return (ePerson2.uuid !== ePerson.uuid); - }); + }); return observableOf(true); }, editEPerson(ePerson: EPerson) { @@ -260,17 +261,16 @@ describe('EPeopleRegistryComponent', () => { describe('delete EPerson button when the isAuthorized returns false', () => { let ePeopleDeleteButton; beforeEach(() => { - authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(false) - }); + spyOn(authorizationService, 'isAuthorized').and.returnValue(observableOf(false)); + component.initialisePage(); + fixture.detectChanges(); }); it('should be disabled', () => { ePeopleDeleteButton = fixture.debugElement.queryAll(By.css('#epeople tr td div button.delete-button')); - ePeopleDeleteButton.forEach((deleteButton) => { + ePeopleDeleteButton.forEach((deleteButton: DebugElement) => { expect(deleteButton.nativeElement.disabled).toBe(true); }); - }); }); }); diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts index b7536177cd..4b65535fce 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; @@ -37,10 +37,10 @@ describe('MembersListComponent', () => { let ePersonDataServiceStub: any; let groupsDataServiceStub: any; let activeGroup; - let allEPersons; - let allGroups; - let epersonMembers; - let subgroupMembers; + let allEPersons: EPerson[]; + let allGroups: Group[]; + let epersonMembers: EPerson[]; + let subgroupMembers: Group[]; let paginationService; beforeEach(waitForAsync(() => { @@ -53,7 +53,7 @@ describe('MembersListComponent', () => { activeGroup: activeGroup, epersonMembers: epersonMembers, subgroupMembers: subgroupMembers, - findListByHref(href: string): Observable>> { + findListByHref(_href: string): Observable>> { return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupsDataServiceStub.getEPersonMembers())); }, searchByScope(scope: string, query: string): Observable>> { @@ -147,6 +147,7 @@ describe('MembersListComponent', () => { }); afterEach(fakeAsync(() => { fixture.destroy(); + fixture.debugElement.nativeElement.remove(); flush(); component = null; fixture.debugElement.nativeElement.remove(); @@ -168,12 +169,19 @@ describe('MembersListComponent', () => { describe('search', () => { describe('when searching without query', () => { - let epersonsFound; + let epersonsFound: DebugElement[]; beforeEach(fakeAsync(() => { + spyOn(component, 'isMemberOfGroup').and.callFake((ePerson: EPerson) => { + return observableOf(activeGroup.epersons.includes(ePerson)); + }); component.search({ scope: 'metadata', query: '' }); tick(); fixture.detectChanges(); epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); + // Stop using the fake spy function (because otherwise the clicking on the buttons will not change anything + // because they don't change the value of activeGroup.epersons) + jasmine.getEnv().allowRespy(true); + spyOn(component, 'isMemberOfGroup').and.callThrough(); })); it('should display all epersons', () => { @@ -182,62 +190,56 @@ describe('MembersListComponent', () => { describe('if eperson is already a eperson', () => { it('should have delete button, else it should have add button', () => { - activeGroup.epersons.map((eperson: EPerson) => { - epersonsFound.map((foundEPersonRowElement) => { - if (foundEPersonRowElement.debugElement !== undefined) { - const epersonId = foundEPersonRowElement.debugElement.query(By.css('td:first-child')); - const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); - const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); - if (epersonId.nativeElement.textContent === eperson.id) { - expect(addButton).toBeUndefined(); - expect(deleteButton).toBeDefined(); - } else { - expect(deleteButton).toBeUndefined(); - expect(addButton).toBeDefined(); - } - } - }); + const memberIds: string[] = activeGroup.epersons.map((ePerson: EPerson) => ePerson.id); + epersonsFound.map((foundEPersonRowElement: DebugElement) => { + const epersonId: DebugElement = foundEPersonRowElement.query(By.css('td:first-child')); + const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus')); + const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt')); + if (memberIds.includes(epersonId.nativeElement.textContent)) { + expect(addButton).toBeNull(); + expect(deleteButton).not.toBeNull(); + } else { + expect(deleteButton).toBeNull(); + expect(addButton).not.toBeNull(); + } }); }); }); describe('if first add button is pressed', () => { beforeEach(fakeAsync(() => { - const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus')); + const addButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus')); addButton.nativeElement.click(); tick(); fixture.detectChanges(); })); - it('all groups in search member of selected group', () => { + it('then all the ePersons are member of the active group', () => { epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); expect(epersonsFound.length).toEqual(2); - epersonsFound.map((foundEPersonRowElement) => { - if (foundEPersonRowElement.debugElement !== undefined) { - const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); - const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); - expect(addButton).toBeUndefined(); - expect(deleteButton).toBeDefined(); - } + epersonsFound.map((foundEPersonRowElement: DebugElement) => { + const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus')); + const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt')); + expect(addButton).toBeNull(); + expect(deleteButton).not.toBeNull(); }); }); }); describe('if first delete button is pressed', () => { beforeEach(fakeAsync(() => { - const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt')); - addButton.nativeElement.click(); + const deleteButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt')); + deleteButton.nativeElement.click(); tick(); fixture.detectChanges(); })); - it('first eperson in search delete button, because now member', () => { + it('then no ePerson is member of the active group', () => { epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); - epersonsFound.map((foundEPersonRowElement) => { - if (foundEPersonRowElement.debugElement !== undefined) { - const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); - const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); - expect(deleteButton).toBeUndefined(); - expect(addButton).toBeDefined(); - } + expect(epersonsFound.length).toEqual(2); + epersonsFound.map((foundEPersonRowElement: DebugElement) => { + const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus')); + const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt')); + expect(deleteButton).toBeNull(); + expect(addButton).not.toBeNull(); }); }); }); diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts index 58d252f0b4..d0fc046093 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts @@ -249,6 +249,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * @param ePerson EPerson we want to delete as member from group that is currently being edited */ deleteMemberFromGroup(ePerson: EpersonDtoModel) { + ePerson.memberOfGroup = false; this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => { if (activeGroup != null) { const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson); diff --git a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html index 3be45c4452..d1574b0dba 100644 --- a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html +++ b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html @@ -65,7 +65,7 @@ -

{{ messagePrefix + '.table.edit.currentGroup' | translate }}

+ {{ messagePrefix + '.table.edit.currentGroup' | translate }}
@@ -17,7 +17,7 @@
+ - +
+
{{ listItem.collection.name}}
+
+ - + diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.spec.ts b/src/app/shared/dso-selector/dso-selector/dso-selector.component.spec.ts index 7c28859388..e2acd17bc0 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.spec.ts +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.spec.ts @@ -11,6 +11,7 @@ import { PaginatedSearchOptions } from '../../search/models/paginated-search-opt import { hasValue } from '../../empty.util'; import { createPaginatedList } from '../../testing/utils.test'; import { NotificationsService } from '../../notifications/notifications.service'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; describe('DSOSelectorComponent', () => { let component: DSOSelectorComponent; @@ -34,7 +35,7 @@ describe('DSOSelectorComponent', () => { ]; const searchService = { - search: (options: PaginatedSearchOptions) => { + search: (options: PaginatedSearchOptions, responseMsToLive?: number, useCachedVersionIfAvailable = true) => { if (hasValue(options.query) && options.query.startsWith('search.resourceid')) { return createSuccessfulRemoteDataObject$(createPaginatedList([searchResult])); } else if (options.pagination.currentPage === 1) { @@ -120,6 +121,43 @@ describe('DSOSelectorComponent', () => { }); }); + describe('search', () => { + beforeEach(() => { + spyOn(searchService, 'search').and.callThrough(); + }); + + it('should specify how to sort if no query is given', () => { + component.sort = new SortOptions('dc.title', SortDirection.ASC); + component.search(undefined, 0); + + expect(searchService.search).toHaveBeenCalledWith( + jasmine.objectContaining({ + query: undefined, + sort: jasmine.objectContaining({ + field: 'dc.title', + direction: SortDirection.ASC, + }), + }), + null, + true + ); + }); + + it('should not specify how to sort if a query is given', () => { + component.sort = new SortOptions('dc.title', SortDirection.ASC); + component.search('testQuery', 0); + + expect(searchService.search).toHaveBeenCalledWith( + jasmine.objectContaining({ + query: 'testQuery', + sort: null, + }), + null, + true + ); + }); + }); + describe('when search returns an error', () => { beforeEach(() => { spyOn(searchService, 'search').and.returnValue(createFailedRemoteDataObject$()); diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts index c8d11891ba..fe64c0a41e 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts @@ -31,6 +31,7 @@ import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../empty.util'; import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { SearchResult } from '../../search/models/search-result.model'; +import { SortOptions } from '../../../core/cache/models/sort-options.model'; import { RemoteData } from '../../../core/data/remote-data'; import { NotificationsService } from '../../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; @@ -69,6 +70,11 @@ export class DSOSelectorComponent implements OnInit, OnDestroy { */ @Input() types: DSpaceObjectType[]; + /** + * The sorting options + */ + @Input() sort: SortOptions; + // list of allowed selectable dsoTypes typesString: string; @@ -221,13 +227,16 @@ export class DSOSelectorComponent implements OnInit, OnDestroy { * @param useCache Whether or not to use the cache */ search(query: string, page: number, useCache: boolean = true): Observable>>> { + // default sort is only used when there is not query + let efectiveSort = query ? null : this.sort; return this.searchService.search( new PaginatedSearchOptions({ query: query, dsoTypes: this.types, pagination: Object.assign({}, this.defaultPagination, { currentPage: page - }) + }), + sort: efectiveSort }), null, useCache, diff --git a/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.ts index 8b38b62378..e0b7c1675b 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.ts @@ -8,7 +8,8 @@ import { getCollectionCreateRoute, COLLECTION_PARENT_PARAMETER } from '../../../../collection-page/collection-page-routing-paths'; - +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { environment } from '../../../../../environments/environment'; /** * Component to wrap a list of existing communities inside a modal * Used to choose a community from to create a new collection in @@ -23,6 +24,7 @@ export class CreateCollectionParentSelectorComponent extends DSOSelectorModalWra selectorTypes = [DSpaceObjectType.COMMUNITY]; action = SelectorActionType.CREATE; header = 'dso-selector.create.collection.sub-level'; + defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { super(activeModal, route); diff --git a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html index 4a22672988..a13be63880 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html @@ -14,6 +14,6 @@
{{'dso-selector.create.community.sub-level' | translate}}
- + diff --git a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts index a7f583df50..77458d9802 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts @@ -12,6 +12,8 @@ import { getCommunityCreateRoute, COMMUNITY_PARENT_PARAMETER } from '../../../../community-page/community-page-routing-paths'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { environment } from '../../../../../environments/environment'; /** * Component to wrap a button - for top communities - @@ -29,6 +31,7 @@ export class CreateCommunityParentSelectorComponent extends DSOSelectorModalWrap objectType = DSpaceObjectType.COMMUNITY; selectorTypes = [DSpaceObjectType.COMMUNITY]; action = SelectorActionType.CREATE; + defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { super(activeModal, route); diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts index b109be0af2..ed8a7b0780 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts @@ -4,6 +4,8 @@ import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.mod import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { environment } from '../../../../../environments/environment'; /** * Component to wrap a list of existing collections inside a modal @@ -21,6 +23,7 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo selectorTypes = [DSpaceObjectType.COLLECTION]; action = SelectorActionType.CREATE; header = 'dso-selector.create.item.sub-level'; + defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); /** * If present this value is used to filter collection list by entity type diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.html b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.html index 85d8797e66..54044f5d79 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.html @@ -6,6 +6,6 @@ diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index 113ca518fd..3f81687c9f 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -5,6 +5,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { hasValue, isNotEmpty } from '../../empty.util'; +import { SortOptions } from '../../../core/cache/models/sort-options.model'; export enum SelectorActionType { CREATE = 'create', @@ -49,6 +50,11 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit { */ action: SelectorActionType; + /** + * Default DSO ordering + */ + defaultSort: SortOptions; + constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) { } diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.ts index cfc2ea282d..fd54cd44ed 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.ts @@ -8,6 +8,8 @@ import { SelectorActionType } from '../dso-selector-modal-wrapper.component'; import { getCollectionEditRoute } from '../../../../collection-page/collection-page-routing-paths'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { environment } from '../../../../../environments/environment'; /** * Component to wrap a list of existing collections inside a modal @@ -22,6 +24,7 @@ export class EditCollectionSelectorComponent extends DSOSelectorModalWrapperComp objectType = DSpaceObjectType.COLLECTION; selectorTypes = [DSpaceObjectType.COLLECTION]; action = SelectorActionType.EDIT; + defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { super(activeModal, route); diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.ts index d73a7b48c5..cf2f97c6d3 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.ts @@ -8,6 +8,8 @@ import { SelectorActionType } from '../dso-selector-modal-wrapper.component'; import { getCommunityEditRoute } from '../../../../community-page/community-page-routing-paths'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { environment } from '../../../../../environments/environment'; /** * Component to wrap a list of existing communities inside a modal @@ -23,6 +25,7 @@ export class EditCommunitySelectorComponent extends DSOSelectorModalWrapperCompo objectType = DSpaceObjectType.COMMUNITY; selectorTypes = [DSpaceObjectType.COMMUNITY]; action = SelectorActionType.EDIT; + defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { super(activeModal, route); diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html new file mode 100644 index 0000000000..85d8797e66 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html @@ -0,0 +1,11 @@ +
+ + +
diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts index 4822849e4c..c1ae583908 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts @@ -14,7 +14,7 @@ import { Item } from '../../../../core/shared/item.model'; @Component({ selector: 'ds-edit-item-selector', - templateUrl: '../dso-selector-modal-wrapper.component.html', + templateUrl: 'edit-item-selector.component.html', }) export class EditItemSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { objectType = DSpaceObjectType.ITEM; diff --git a/src/app/shared/file-download-link/file-download-link.component.html b/src/app/shared/file-download-link/file-download-link.component.html index ba81ee3d20..8ebe622a5b 100644 --- a/src/app/shared/file-download-link/file-download-link.component.html +++ b/src/app/shared/file-download-link/file-download-link.component.html @@ -1,5 +1,5 @@ - + diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html index d518d59da2..dd19e6158d 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html @@ -12,12 +12,11 @@ [formGroupName]="idx" [ngClass]="[getClass('element', 'group'), getClass('grid', 'group')]" cdkDrag - cdkDragHandle [cdkDragDisabled]="dragDisabled" [cdkDragPreviewClass]="'ds-submission-reorder-dragging'" [class.grey-background]="model.isInlineGroupArray"> -
+
diff --git a/src/app/shared/form/chips/chips.component.html b/src/app/shared/form/chips/chips.component.html index 2233c1bd16..a6b90b23aa 100644 --- a/src/app/shared/form/chips/chips.component.html +++ b/src/app/shared/form/chips/chips.component.html @@ -1,5 +1,5 @@
- +
diff --git a/src/app/shared/form/chips/chips.component.spec.ts b/src/app/shared/form/chips/chips.component.spec.ts index 2b8a469bd1..050950ed4d 100644 --- a/src/app/shared/form/chips/chips.component.spec.ts +++ b/src/app/shared/form/chips/chips.component.spec.ts @@ -122,7 +122,7 @@ describe('ChipsComponent test suite', () => { })); it('should save chips item index when drag and drop start', fakeAsync(() => { - const de = chipsFixture.debugElement.query(By.css('li.nav-item')); + const de = chipsFixture.debugElement.query(By.css('div.nav-item')); de.triggerEventHandler('dragstart', null); @@ -131,7 +131,7 @@ describe('ChipsComponent test suite', () => { it('should update chips item order when drag and drop end', fakeAsync(() => { spyOn(chipsComp.chips, 'updateOrder'); - const de = chipsFixture.debugElement.query(By.css('li.nav-item')); + const de = chipsFixture.debugElement.query(By.css('div.nav-item')); de.triggerEventHandler('dragend', null); @@ -158,7 +158,7 @@ describe('ChipsComponent test suite', () => { }); it('should show icon for every field that has a configured icon', () => { - const de = chipsFixture.debugElement.query(By.css('li.nav-item')); + const de = chipsFixture.debugElement.query(By.css('div.nav-item')); const icons = de.queryAll(By.css('i.fas')); expect(icons.length).toBe(4); @@ -166,7 +166,7 @@ describe('ChipsComponent test suite', () => { }); it('should show tooltip on mouse over an icon', () => { - const de = chipsFixture.debugElement.query(By.css('li.nav-item')); + const de = chipsFixture.debugElement.query(By.css('div.nav-item')); const icons = de.queryAll(By.css('i.fas')); icons[0].triggerEventHandler('mouseover', null); diff --git a/src/app/shared/lang-switch/lang-switch.component.spec.ts b/src/app/shared/lang-switch/lang-switch.component.spec.ts index 7757622f4c..6d3c847086 100644 --- a/src/app/shared/lang-switch/lang-switch.component.spec.ts +++ b/src/app/shared/lang-switch/lang-switch.component.spec.ts @@ -109,7 +109,7 @@ describe('LangSwitchComponent', () => { })); it('should define the main A HREF in the UI', (() => { - expect(langSwitchElement.querySelector('a')).toBeDefined(); + expect(langSwitchElement.querySelector('a')).not.toBeNull(); })); describe('when selecting a language', () => { diff --git a/src/app/shared/log-in/log-in.component.html b/src/app/shared/log-in/log-in.component.html index 36f7034f4d..6b1fdb9ff6 100644 --- a/src/app/shared/log-in/log-in.component.html +++ b/src/app/shared/log-in/log-in.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts index 86c3010287..3301435956 100644 --- a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts +++ b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts @@ -4,6 +4,8 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../../dso-selector/modal-wrappers/dso-selector-modal-wrapper.component'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { environment } from '../../../../environments/environment'; /** * Component to wrap a button - to select the entire repository - @@ -33,6 +35,11 @@ export class ScopeSelectorModalComponent extends DSOSelectorModalWrapperComponen */ scopeChange = new EventEmitter(); + /** + * Default DSO ordering + */ + defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); + constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) { super(activeModal, route); } 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 74526ad2ad..ab4c57d9f5 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 @@ -7,6 +7,7 @@ import { SearchService } from '../../../../core/shared/search/search.service'; import { currentPath } from '../../../utils/route.utils'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; +import { stripOperatorFromFilterValue } from '../../search.utils'; @Component({ selector: 'ds-search-label', @@ -83,7 +84,8 @@ export class SearchLabelComponent implements OnInit { normalizeFilterValue(value: string) { // const pattern = /,[^,]*$/g; const pattern = /,authority*$/g; - return value.replace(pattern, ''); + value = value.replace(pattern, ''); + return stripOperatorFromFilterValue(value); } private getFilterName(): string { diff --git a/src/app/shared/search/search-labels/search-labels.component.ts b/src/app/shared/search/search-labels/search-labels.component.ts index 8f77d73b21..2cc1919f50 100644 --- a/src/app/shared/search/search-labels/search-labels.component.ts +++ b/src/app/shared/search/search-labels/search-labels.component.ts @@ -4,7 +4,6 @@ import { Observable } from 'rxjs'; import { Params, Router } from '@angular/router'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { map } from 'rxjs/operators'; -import { stripOperatorFromFilterValue } from '../search.utils'; @Component({ selector: 'ds-search-labels', @@ -37,7 +36,7 @@ export class SearchLabelsComponent { const labels = {}; Object.keys(params) .forEach((key) => { - labels[key] = [...params[key].map((value) => stripOperatorFromFilterValue(value))]; + labels[key] = [...params[key].map((value) => value)]; }); return labels; }) diff --git a/src/app/shared/search/search-settings/search-settings.component.spec.ts b/src/app/shared/search/search-settings/search-settings.component.spec.ts index 06e506ddb0..d0b51f04b1 100644 --- a/src/app/shared/search/search-settings/search-settings.component.spec.ts +++ b/src/app/shared/search/search-settings/search-settings.component.spec.ts @@ -107,6 +107,7 @@ describe('SearchSettingsComponent', () => { new SortOptions('dc.title', SortDirection.ASC), new SortOptions('dc.title', SortDirection.DESC) ]; + comp.currentSortOption = new SortOptions('score', SortDirection.DESC); // SearchPageComponent test instance fixture.detectChanges(); @@ -133,7 +134,8 @@ describe('SearchSettingsComponent', () => { it('should have the proper order value selected by default', () => { fixture.detectChanges(); const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings')); - const childElementToBeSelected = orderSetting.query(By.css('option[value="score,DESC"][selected="selected"]')); - expect(childElementToBeSelected).toBeDefined(); + const childElementToBeSelected = orderSetting.query(By.css('option[value="score,DESC"]')); + expect(childElementToBeSelected).not.toBeNull(); + expect(childElementToBeSelected.nativeElement.selected).toBeTrue(); }); }); diff --git a/src/app/shared/search/search-switch-configuration/search-switch-configuration.component.spec.ts b/src/app/shared/search/search-switch-configuration/search-switch-configuration.component.spec.ts index fadde46e53..8fc178c67a 100644 --- a/src/app/shared/search/search-switch-configuration/search-switch-configuration.component.spec.ts +++ b/src/app/shared/search/search-switch-configuration/search-switch-configuration.component.spec.ts @@ -78,7 +78,7 @@ describe('SearchSwitchConfigurationComponent', () => { it('should display select field properly', () => { const selectField = fixture.debugElement.query(By.css('.form-control')); - expect(selectField).toBeDefined(); + expect(selectField).not.toBeNull(); const childElements = selectField.children; expect(childElements.length).toEqual(comp.configurationList.length); diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index f90814f185..adcffd9b5d 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -188,7 +188,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { * Reset search form control on dropdown menu close */ onClose() { - this.collectionDropdown.reset(); + this.collectionDropdown?.reset(); } /** @@ -199,7 +199,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { */ toggled(isOpen: boolean) { if (!isOpen) { - this.collectionDropdown.reset(); + this.collectionDropdown?.reset(); } } } diff --git a/src/app/submission/form/section-add/submission-form-section-add.component.spec.ts b/src/app/submission/form/section-add/submission-form-section-add.component.spec.ts index 971b15d7b0..a8ad7fbe49 100644 --- a/src/app/submission/form/section-add/submission-form-section-add.component.spec.ts +++ b/src/app/submission/form/section-add/submission-form-section-add.component.spec.ts @@ -96,6 +96,7 @@ describe('SubmissionFormSectionAddComponent Component', () => { afterEach(() => { testFixture.destroy(); + testFixture.debugElement.nativeElement.remove(); }); it('should create SubmissionFormSectionAddComponent', inject([SubmissionFormSectionAddComponent], (app: SubmissionFormSectionAddComponent) => { @@ -163,7 +164,7 @@ describe('SubmissionFormSectionAddComponent Component', () => { it('should have dropdown menu closed', () => { - expect(dropdowBtn).not.toBeUndefined(); + expect(dropdowBtn).not.toBeNull(); expect(dropdownMenu.nativeElement.classList).not.toContain('show'); }); diff --git a/src/app/submission/sections/container/section-container.component.spec.ts b/src/app/submission/sections/container/section-container.component.spec.ts index 7568b17ea7..d3f4a93762 100644 --- a/src/app/submission/sections/container/section-container.component.spec.ts +++ b/src/app/submission/sections/container/section-container.component.spec.ts @@ -137,7 +137,7 @@ describe('SubmissionSectionContainerComponent test suite', () => { const section = fixture.debugElement.query(By.css('[id^=\'sectionContent_\']')); expect(comp.getSectionContent).toHaveBeenCalled(); - expect(section).toBeDefined(); + expect(section).not.toBeNull(); }); it('should call removeSection properly', () => { @@ -165,7 +165,7 @@ describe('SubmissionSectionContainerComponent test suite', () => { fixture.detectChanges(); sectionErrorsDiv = fixture.debugElement.query(By.css('[id^=\'sectionGenericError_\']')); - expect(sectionErrorsDiv).toBeDefined(); + expect(sectionErrorsDiv).not.toBeNull(); }); it('should display warning icon', () => { @@ -180,7 +180,7 @@ describe('SubmissionSectionContainerComponent test suite', () => { const iconWarn = fixture.debugElement.query(By.css('i.text-warning')); const iconErr = fixture.debugElement.query(By.css('i.text-danger')); const iconSuccess = fixture.debugElement.query(By.css('i.text-success')); - expect(iconWarn).toBeDefined(); + expect(iconWarn).not.toBeNull(); expect(iconErr).toBeNull(); expect(iconSuccess).toBeNull(); }); @@ -198,7 +198,7 @@ describe('SubmissionSectionContainerComponent test suite', () => { const iconErr = fixture.debugElement.query(By.css('i.text-danger')); const iconSuccess = fixture.debugElement.query(By.css('i.text-success')); expect(iconWarn).toBeNull(); - expect(iconErr).toBeDefined(); + expect(iconErr).not.toBeNull(); expect(iconSuccess).toBeNull(); }); @@ -216,7 +216,7 @@ describe('SubmissionSectionContainerComponent test suite', () => { const iconSuccess = fixture.debugElement.query(By.css('i.text-success')); expect(iconWarn).toBeNull(); expect(iconErr).toBeNull(); - expect(iconSuccess).toBeDefined(); + expect(iconSuccess).not.toBeNull(); }); }); @@ -224,7 +224,8 @@ describe('SubmissionSectionContainerComponent test suite', () => { // declare a test component @Component({ - selector: 'ds-test-cmp', + // eslint-disable-next-line @angular-eslint/component-selector + selector: '', template: `` }) class TestComponent { diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index fc4c6aa74d..a8fc1b1177 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4694,7 +4694,7 @@ "submission.workflow.generic.delete": "Delete", - "submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.", + "submission.workflow.generic.delete-help": "Select this option to discard this item. You will then be asked to confirm it.", "submission.workflow.generic.edit": "Edit", diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index d62b9e5bcb..e3f8988744 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -21,6 +21,7 @@ import { CommunityListConfig } from './community-list-config.interface'; import { HomeConfig } from './homepage-config.interface'; import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; +import { DiscoverySortConfig } from './discovery-sort.config'; interface AppConfig extends Config { ui: UIServerConfig; @@ -46,6 +47,7 @@ interface AppConfig extends Config { info: InfoConfig; markdown: MarkdownConfig; vocabularies: FilterVocabularyConfig[]; + comcolSelectionSort: DiscoverySortConfig; } /** diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index e7851d4b34..67c2feada8 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -21,6 +21,7 @@ import { CommunityListConfig } from './community-list-config.interface'; import { HomeConfig } from './homepage-config.interface'; import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; +import { DiscoverySortConfig } from './discovery-sort.config'; export class DefaultAppConfig implements AppConfig { production = false; @@ -421,4 +422,10 @@ export class DefaultAppConfig implements AppConfig { enabled: false } ]; + + // Configuration that determines the metadata sorting of community and collection edition and creation when there are not a search query. + comcolSelectionSort: DiscoverySortConfig = { + sortField:'dc.title', + sortDirection:'ASC', + }; } diff --git a/src/config/discovery-sort.config.ts b/src/config/discovery-sort.config.ts new file mode 100644 index 0000000000..c0d5e5e391 --- /dev/null +++ b/src/config/discovery-sort.config.ts @@ -0,0 +1,14 @@ +import { Config } from './config.interface'; + +/** + * Config that determines a metadata sorting config. + * It's created mainly to sort by metadata community and collection edition and creation + */ +export class DiscoverySortConfig implements Config { + + public sortField: string; + /** + * ASC / DESC values expected + */ + public sortDirection: string; +} diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 0bb36da61f..08d7739f7d 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -297,6 +297,10 @@ export const environment: BuildConfig = { enabled: false, mathjax: false, }, + comcolSelectionSort: { + sortField:'dc.title', + sortDirection:'ASC', + }, vocabularies: [ { diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index 60cbf40082..b26e200875 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -246,3 +246,8 @@ ul.dso-edit-menu-dropdown > li .nav-item.nav-link { padding: 0; display: inline; } + +.table th, +.table td { + vertical-align: middle; +} diff --git a/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html b/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html index 85d8797e66..54044f5d79 100644 --- a/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html +++ b/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html @@ -6,6 +6,6 @@ diff --git a/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html b/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html index 4a22672988..a13be63880 100644 --- a/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html +++ b/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html @@ -14,6 +14,6 @@
{{'dso-selector.create.community.sub-level' | translate}}
- + diff --git a/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.html b/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.html index 85d8797e66..54044f5d79 100644 --- a/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.html +++ b/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component.html @@ -6,6 +6,6 @@ diff --git a/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.html b/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.html index 85d8797e66..54044f5d79 100644 --- a/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.html +++ b/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component.html @@ -6,6 +6,6 @@