mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'w2p-100414_Missing-search-result-statistics-7.4' into w2p-100414_Missing-search-result-statistics-PR
This commit is contained in:
@@ -333,8 +333,9 @@ export class SearchService implements OnDestroy {
|
||||
* Send search event to rest api using angularitics
|
||||
* @param config Paginated search options used
|
||||
* @param searchQueryResponse The response objects of the performed search
|
||||
* @param object Optional UUID of an object a search was performed and clicked for
|
||||
*/
|
||||
trackSearch(config: PaginatedSearchOptions, searchQueryResponse: SearchObjects<DSpaceObject>) {
|
||||
trackSearch(config: PaginatedSearchOptions, searchQueryResponse: SearchObjects<DSpaceObject>, object?: string) {
|
||||
const filters: { filter: string, operator: string, value: string, label: string; }[] = [];
|
||||
const appliedFilters = searchQueryResponse.appliedFilters || [];
|
||||
for (let i = 0, filtersLength = appliedFilters.length; i < filtersLength; i++) {
|
||||
@@ -356,6 +357,7 @@ export class SearchService implements OnDestroy {
|
||||
order: config.sort.direction
|
||||
},
|
||||
filters: filters,
|
||||
object,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import { CoreModule } from '../core/core.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||
import { SearchTrackerComponent } from './search-tracker.component';
|
||||
import { StatisticsModule } from '../statistics/statistics.module';
|
||||
import { SearchPageComponent } from './search-page.component';
|
||||
import { SearchFilterService } from '../core/shared/search/search-filter.service';
|
||||
@@ -16,7 +15,6 @@ import { SearchModule } from '../shared/search/search.module';
|
||||
|
||||
const components = [
|
||||
SearchPageComponent,
|
||||
SearchTrackerComponent,
|
||||
ThemedSearchPageComponent
|
||||
];
|
||||
|
||||
|
@@ -1 +0,0 @@
|
||||
|
@@ -1,3 +0,0 @@
|
||||
:host {
|
||||
display: none
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { SearchComponent } from '../shared/search/search.component';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../my-dspace-page/my-dspace-page.component';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
import { SearchService } from '../core/shared/search/search.service';
|
||||
import { PaginatedSearchOptions } from '../shared/search/models/paginated-search-options.model';
|
||||
import { SearchObjects } from '../shared/search/models/search-objects.model';
|
||||
import { Router } from '@angular/router';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { getFirstSucceededRemoteData } from '../core/shared/operators';
|
||||
|
||||
/**
|
||||
* This component triggers a page view statistic
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-search-tracker',
|
||||
styleUrls: ['./search-tracker.component.scss'],
|
||||
templateUrl: './search-tracker.component.html',
|
||||
providers: [
|
||||
{
|
||||
provide: SEARCH_CONFIG_SERVICE,
|
||||
useClass: SearchConfigurationService
|
||||
}
|
||||
]
|
||||
})
|
||||
export class SearchTrackerComponent extends SearchComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
protected service: SearchService,
|
||||
protected sidebarService: SidebarService,
|
||||
protected windowService: HostWindowService,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||
protected routeService: RouteService,
|
||||
public angulartics2: Angulartics2,
|
||||
protected router: Router
|
||||
) {
|
||||
super(service, sidebarService, windowService, searchConfigService, routeService, router);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// super.ngOnInit();
|
||||
this.getSearchOptions().pipe(
|
||||
switchMap((options: PaginatedSearchOptions) =>
|
||||
this.service.searchEntries(options).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
map((rd: RemoteData<SearchObjects<DSpaceObject>>) => ({
|
||||
config: options,
|
||||
searchQueryResponse: rd.payload
|
||||
}))
|
||||
)),
|
||||
).subscribe(({ config, searchQueryResponse }) => {
|
||||
const filters: { filter: string, operator: string, value: string, label: string; }[] = [];
|
||||
const appliedFilters = searchQueryResponse.appliedFilters || [];
|
||||
for (let i = 0, filtersLength = appliedFilters.length; i < filtersLength; i++) {
|
||||
const appliedFilter = appliedFilters[i];
|
||||
filters.push(appliedFilter);
|
||||
}
|
||||
this.angulartics2.eventTrack.next({
|
||||
action: 'search',
|
||||
properties: {
|
||||
searchOptions: config,
|
||||
page: {
|
||||
size: config.pagination.size, // same as searchQueryResponse.page.elementsPerPage
|
||||
totalElements: searchQueryResponse.pageInfo.totalElements,
|
||||
totalPages: searchQueryResponse.pageInfo.totalPages,
|
||||
number: config.pagination.currentPage, // same as searchQueryResponse.page.currentPage
|
||||
},
|
||||
sort: {
|
||||
by: config.sort.field,
|
||||
order: config.sort.direction
|
||||
},
|
||||
filters: filters,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
|
||||
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
|
||||
import { debounceTime, distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import uniqueId from 'lodash/uniqueId';
|
||||
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
@@ -11,7 +11,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { pushInOut } from '../animations/push';
|
||||
import { HostWindowService } from '../host-window.service';
|
||||
import { SidebarService } from '../sidebar/sidebar.service';
|
||||
import { hasValue } from '../empty.util';
|
||||
import { hasValue, hasValueOperator, isNotEmpty } from '../empty.util';
|
||||
import { RouteService } from '../../core/services/route.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
||||
import { PaginatedSearchOptions } from './models/paginated-search-options.model';
|
||||
@@ -35,6 +35,9 @@ import { environment } from 'src/environments/environment';
|
||||
import { SubmissionObject } from '../../core/submission/models/submission-object.model';
|
||||
import { SearchFilterConfig } from './models/search-filter-config.model';
|
||||
import { WorkspaceItem } from '../..//core/submission/models/workspaceitem.model';
|
||||
import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths';
|
||||
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
||||
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search',
|
||||
@@ -229,9 +232,21 @@ export class SearchComponent implements OnInit {
|
||||
searchLink: string;
|
||||
|
||||
/**
|
||||
* Subscription to unsubscribe from
|
||||
* Regex to match UUIDs
|
||||
*/
|
||||
sub: Subscription;
|
||||
uuidRegex = /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/g;
|
||||
|
||||
/**
|
||||
* List of paths that are considered to be the start of a route to an object page (excluding "/", e.g. "items")
|
||||
* These are expected to end on an object UUID
|
||||
* If they match the route we're navigating to, an object property will be added to the search event sent
|
||||
*/
|
||||
allowedObjectPaths: string[] = ['entities', ITEM_MODULE_PATH, COLLECTION_MODULE_PATH, COMMUNITY_MODULE_PATH];
|
||||
|
||||
/**
|
||||
* Subscriptions to unsubscribe from
|
||||
*/
|
||||
subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* Emits an event with the current search result entries
|
||||
@@ -301,7 +316,7 @@ export class SearchComponent implements OnInit {
|
||||
);
|
||||
const searchOptions$: Observable<PaginatedSearchOptions> = this.getSearchOptions().pipe(distinctUntilChanged());
|
||||
|
||||
this.sub = combineLatest([configuration$, searchSortOptions$, searchOptions$, sortOption$]).pipe(
|
||||
this.subs.push(combineLatest([configuration$, searchSortOptions$, searchOptions$, sortOption$]).pipe(
|
||||
filter(([configuration, searchSortOptions, searchOptions, sortOption]: [string, SortOptions[], PaginatedSearchOptions, SortOptions]) => {
|
||||
// filter for search options related to instanced paginated id
|
||||
return searchOptions.pagination.id === this.paginationId;
|
||||
@@ -332,7 +347,9 @@ export class SearchComponent implements OnInit {
|
||||
this.retrieveSearchResults(newSearchOptions);
|
||||
this.retrieveFilters(searchOptions);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.subscribeToRoutingEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,12 +391,10 @@ export class SearchComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the subscription
|
||||
* Unsubscribe from the subscriptions
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (hasValue(this.sub)) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,6 +455,43 @@ export class SearchComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to routing events to detect when a user moves away from the search page
|
||||
* When the user is routing to an object page, it needs to send out a separate search event containing that object's UUID
|
||||
* This method should only be called once and is essentially what SearchTrackingComponent used to do (now removed)
|
||||
* @private
|
||||
*/
|
||||
private subscribeToRoutingEvents() {
|
||||
this.subs.push(
|
||||
this.router.events.pipe(
|
||||
filter((event) => event instanceof NavigationStart),
|
||||
map((event: NavigationStart) => this.getDsoUUIDFromUrl(event.url)),
|
||||
hasValueOperator(),
|
||||
).subscribe((uuid) => {
|
||||
if (this.resultsRD$.value.hasSucceeded) {
|
||||
this.service.trackSearch(this.searchOptions$.value, this.resultsRD$.value.payload as SearchObjects<DSpaceObject>, uuid);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UUID from a DSO url
|
||||
* Return null if the url isn't an object page (allowedObjectPaths) or the UUID couldn't be found
|
||||
* @param url
|
||||
*/
|
||||
private getDsoUUIDFromUrl(url: string): string {
|
||||
if (isNotEmpty(url)) {
|
||||
if (this.allowedObjectPaths.some((path) => url.startsWith(`/${path}`))) {
|
||||
const uuid = url.substring(url.lastIndexOf('/') + 1);
|
||||
if (uuid.match(this.uuidRegex)) {
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the sidebar is collapsed
|
||||
* @returns {Observable<boolean>} emits true if the sidebar is currently collapsed, false if it is expanded
|
||||
|
@@ -31,7 +31,8 @@ export class Angulartics2DSpace {
|
||||
event.properties.searchOptions,
|
||||
event.properties.page,
|
||||
event.properties.sort,
|
||||
event.properties.filters
|
||||
event.properties.filters,
|
||||
event.properties.clickedObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -45,12 +45,14 @@ export class StatisticsService {
|
||||
* @param page: An object that describes the pagination status
|
||||
* @param sort: An object that describes the sort status
|
||||
* @param filters: An array of search filters used to filter the result set
|
||||
* @param clickedObject: UUID of object clicked
|
||||
*/
|
||||
trackSearchEvent(
|
||||
searchOptions: SearchOptions,
|
||||
page: { size: number, totalElements: number, totalPages: number, number: number },
|
||||
sort: { by: string, order: string },
|
||||
filters?: { filter: string, operator: string, value: string, label: string }[]
|
||||
filters?: { filter: string, operator: string, value: string, label: string }[],
|
||||
clickedObject?: string,
|
||||
) {
|
||||
const body = {
|
||||
query: searchOptions.query,
|
||||
@@ -87,6 +89,9 @@ export class StatisticsService {
|
||||
}
|
||||
Object.assign(body, { appliedFilters: bodyFilters });
|
||||
}
|
||||
if (hasValue(clickedObject)) {
|
||||
Object.assign(body, { clickedObject });
|
||||
}
|
||||
this.sendEvent('/statistics/searchevents', body);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user