Merge branch 'w2p-100414_Missing-search-result-statistics-7.4' into w2p-100414_Missing-search-result-statistics-PR

This commit is contained in:
Kristof De Langhe
2023-05-11 11:36:24 +02:00
8 changed files with 74 additions and 103 deletions

View File

@@ -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,
},
});
}

View File

@@ -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
];

View File

@@ -1 +0,0 @@
&nbsp;

View File

@@ -1,3 +0,0 @@
:host {
display: none
}

View File

@@ -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,
},
});
});
}
}

View File

@@ -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

View File

@@ -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,
);
}
}

View File

@@ -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);
}