forked from DSpace/dspace-angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Geospatial maps for item pages, search, browse
- Loading branch information
Showing
46 changed files
with
1,626 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/app/browse-by/browse-by-geospatial-data/browse-by-geospatial-data.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<div class="container"> | ||
<h1>{{ 'browse.metadata.map' | translate }}</h1> | ||
<ng-container *ngIf="isPlatformBrowser(platformId)"> | ||
<ds-geospatial-map [facetValues]="facetValues$" | ||
[currentScope]="this.scope$|async" | ||
[layout]="'browse'" | ||
style="width: 100%;"> | ||
</ds-geospatial-map> | ||
</ng-container> | ||
</div> | ||
|
Empty file.
149 changes: 149 additions & 0 deletions
149
src/app/browse-by/browse-by-geospatial-data/browse-by-geospatial-data.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { NO_ERRORS_SCHEMA } from '@angular/core'; | ||
import { | ||
async, | ||
ComponentFixture, | ||
TestBed, | ||
waitForAsync, | ||
} from '@angular/core/testing'; | ||
import { ActivatedRoute } from '@angular/router'; | ||
import { StoreModule } from '@ngrx/store'; | ||
import { TranslateModule } from '@ngx-translate/core'; | ||
import { of as observableOf } from 'rxjs'; | ||
|
||
import { environment } from '../../../environments/environment'; | ||
import { buildPaginatedList } from '../../core/data/paginated-list.model'; | ||
import { PageInfo } from '../../core/shared/page-info.model'; | ||
import { SearchService } from '../../core/shared/search/search.service'; | ||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; | ||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; | ||
import { FacetValue } from '../../shared/search/models/facet-value.model'; | ||
import { FilterType } from '../../shared/search/models/filter-type.model'; | ||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; | ||
import { SearchFilterConfig } from '../../shared/search/models/search-filter-config.model'; | ||
import { SearchServiceStub } from '../../shared/testing/search-service.stub'; | ||
import { BrowseByGeospatialDataComponent } from './browse-by-geospatial-data.component'; | ||
|
||
// create route stub | ||
const scope = 'test scope'; | ||
const activatedRouteStub = { | ||
queryParams: observableOf({ | ||
scope: scope, | ||
}), | ||
}; | ||
|
||
// Mock search filter config | ||
const mockFilterConfig = Object.assign(new SearchFilterConfig(), { | ||
name: 'point', | ||
type: FilterType.text, | ||
hasFacets: true, | ||
isOpenByDefault: false, | ||
pageSize: 2, | ||
minValue: 200, | ||
maxValue: 3000, | ||
}); | ||
|
||
// Mock facet values with and without point data | ||
const facetValue: FacetValue = { | ||
label: 'test', | ||
value: 'test', | ||
count: 20, | ||
_links: { | ||
self: { href: 'selectedValue-self-link2' }, | ||
search: { href: `` }, | ||
}, | ||
}; | ||
const pointFacetValue: FacetValue = { | ||
label: 'test point', | ||
value: 'Point ( +174.000000 -042.000000 )', | ||
count: 20, | ||
_links: { | ||
self: { href: 'selectedValue-self-link' }, | ||
search: { href: `` }, | ||
}, | ||
}; | ||
const mockValues = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [facetValue])); | ||
const mockPointValues = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [pointFacetValue])); | ||
|
||
// Expected search options used in getFacetValuesFor call | ||
const expectedSearchOptions: PaginatedSearchOptions = Object.assign({ | ||
'configuration': environment.geospatialMapViewer.spatialFacetDiscoveryConfiguration, | ||
'scope': scope, | ||
}); | ||
|
||
// Mock search config service returns mock search filter config on getConfig() | ||
const mockSearchConfigService = jasmine.createSpyObj('searchConfigurationService', { | ||
getConfig: createSuccessfulRemoteDataObject$([mockFilterConfig]), | ||
}); | ||
let searchService: SearchServiceStub = new SearchServiceStub(); | ||
|
||
// initialize testing environment | ||
describe('BrowseByGeospatialDataComponent', () => { | ||
let component: BrowseByGeospatialDataComponent; | ||
let fixture: ComponentFixture<BrowseByGeospatialDataComponent>; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [ TranslateModule.forRoot(), StoreModule.forRoot(), BrowseByGeospatialDataComponent], | ||
providers: [ | ||
{ provide: SearchService, useValue: searchService }, | ||
{ provide: SearchConfigurationService, useValue: mockSearchConfigService }, | ||
{ provide: ActivatedRoute, useValue: activatedRouteStub }, | ||
], | ||
schemas: [NO_ERRORS_SCHEMA], | ||
}) | ||
.compileComponents(); | ||
})); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(BrowseByGeospatialDataComponent); | ||
component = fixture.componentInstance; | ||
}); | ||
|
||
it('component should be created successfully', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
// return this.searchService.getFacetValuesFor(searchFilterConfig, 1, searchOptions, | ||
// null, true); | ||
describe('BrowseByGeospatialDataComponent component with valid facet values', () => { | ||
beforeEach(() => { | ||
fixture = TestBed.createComponent(BrowseByGeospatialDataComponent); | ||
component = fixture.componentInstance; | ||
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockPointValues); | ||
component.scope$ = observableOf(''); | ||
component.ngOnInit(); | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should call searchConfigService.getConfig() after init', waitForAsync(() => { | ||
expect(mockSearchConfigService.getConfig).toHaveBeenCalled(); | ||
})); | ||
|
||
it('should call searchService.getFacetValuesFor() with expected parameters', waitForAsync(() => { | ||
component.getFacetValues().subscribe(() => { | ||
expect(searchService.getFacetValuesFor).toHaveBeenCalledWith(mockFilterConfig, 1, expectedSearchOptions, null, true); | ||
}); | ||
})); | ||
}); | ||
|
||
describe('BrowseByGeospatialDataComponent component with invalid facet values (no point data)', () => { | ||
beforeEach(() => { | ||
fixture = TestBed.createComponent(BrowseByGeospatialDataComponent); | ||
component = fixture.componentInstance; | ||
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues); | ||
component.scope$ = observableOf(''); | ||
component.ngOnInit(); | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should call searchConfigService.getConfig() after init', waitForAsync(() => { | ||
expect(mockSearchConfigService.getConfig).toHaveBeenCalled(); | ||
})); | ||
|
||
it('should call searchService.getFacetValuesFor() with expected parameters', waitForAsync(() => { | ||
component.getFacetValues().subscribe(() => { | ||
expect(searchService.getFacetValuesFor).toHaveBeenCalledWith(mockFilterConfig, 1, expectedSearchOptions, null, true); | ||
}); | ||
})); | ||
}); | ||
|
||
}); |
109 changes: 109 additions & 0 deletions
109
src/app/browse-by/browse-by-geospatial-data/browse-by-geospatial-data.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { | ||
AsyncPipe, | ||
isPlatformBrowser, | ||
NgIf, | ||
} from '@angular/common'; | ||
import { | ||
ChangeDetectionStrategy, | ||
Component, | ||
Inject, | ||
OnInit, | ||
PLATFORM_ID, | ||
} from '@angular/core'; | ||
import { | ||
ActivatedRoute, | ||
Params, | ||
} from '@angular/router'; | ||
import { TranslateModule } from '@ngx-translate/core'; | ||
import { | ||
combineLatest, | ||
Observable, | ||
of, | ||
} from 'rxjs'; | ||
import { | ||
filter, | ||
map, | ||
switchMap, | ||
take, | ||
} from 'rxjs/operators'; | ||
|
||
import { environment } from '../../../environments/environment'; | ||
import { | ||
getFirstCompletedRemoteData, | ||
getFirstSucceededRemoteDataPayload, | ||
} from '../../core/shared/operators'; | ||
import { SearchService } from '../../core/shared/search/search.service'; | ||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; | ||
import { hasValue } from '../../shared/empty.util'; | ||
import { GeospatialMapComponent } from '../../shared/geospatial-map/geospatial-map.component'; | ||
import { FacetValues } from '../../shared/search/models/facet-values.model'; | ||
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; | ||
|
||
@Component({ | ||
selector: 'ds-browse-by-geospatial-data', | ||
templateUrl: './browse-by-geospatial-data.component.html', | ||
styleUrls: ['./browse-by-geospatial-data.component.scss'], | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
imports: [GeospatialMapComponent, NgIf, AsyncPipe, TranslateModule], | ||
standalone: true, | ||
}) | ||
/** | ||
* Component displaying a large 'browse map', which is really a geolocation few of the 'point' facet defined | ||
* in the geospatial discovery configuration. | ||
* The markers are clustered by location, and each individual marker will link to a search page for that point value | ||
* as a filter. | ||
* | ||
* @author Kim Shepherd | ||
*/ | ||
export class BrowseByGeospatialDataComponent implements OnInit { | ||
|
||
protected readonly isPlatformBrowser = isPlatformBrowser; | ||
|
||
public facetValues$: Observable<FacetValues> = of(null); | ||
|
||
constructor( | ||
@Inject(PLATFORM_ID) public platformId: string, | ||
private searchConfigurationService: SearchConfigurationService, | ||
private searchService: SearchService, | ||
protected route: ActivatedRoute, | ||
) {} | ||
|
||
public scope$: Observable<string> ; | ||
|
||
ngOnInit(): void { | ||
this.scope$ = this.route.queryParams.pipe( | ||
map((params: Params) => params.scope), | ||
); | ||
this.facetValues$ = this.getFacetValues(); | ||
} | ||
|
||
/** | ||
* Get facet values for use in rendering 'browse by' geospatial map | ||
*/ | ||
getFacetValues(): Observable<FacetValues> { | ||
return combineLatest([this.scope$, this.searchConfigurationService.getConfig( | ||
// If the geospatial configuration is not found, default will be returned and used | ||
'', environment.geospatialMapViewer.spatialFacetDiscoveryConfiguration).pipe( | ||
getFirstCompletedRemoteData(), | ||
getFirstSucceededRemoteDataPayload(), | ||
filter((searchFilterConfigs) => hasValue(searchFilterConfigs)), | ||
take(1), | ||
map((searchFilterConfigs) => searchFilterConfigs[0]), | ||
filter((searchFilterConfig) => hasValue(searchFilterConfig))), | ||
], | ||
).pipe( | ||
switchMap(([scope, searchFilterConfig]) => { | ||
// Get all points in one page, if possible | ||
searchFilterConfig.pageSize = 99999; | ||
const searchOptions: PaginatedSearchOptions = Object.assign({ | ||
'configuration': environment.geospatialMapViewer.spatialFacetDiscoveryConfiguration, | ||
'scope': scope, | ||
}); | ||
return this.searchService.getFacetValuesFor(searchFilterConfig, 1, searchOptions, | ||
null, true); | ||
}), | ||
getFirstCompletedRemoteData(), | ||
getFirstSucceededRemoteDataPayload(), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.