Skip to content

Commit

Permalink
Added tracking of empty result set. Resolves #1229. (#1235)
Browse files Browse the repository at this point in the history
  • Loading branch information
MillenniumFalconMechanic authored Jun 16, 2020
1 parent f063da6 commit b9c5f91
Show file tree
Hide file tree
Showing 35 changed files with 301 additions and 154 deletions.
2 changes: 2 additions & 0 deletions spa/src/app/_ngrx/app.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { MatrixEffects } from "../files/_ngrx/matrix/matrix.effects";
import { ProjectEffects } from "../files/_ngrx/project/project.effects";
import { ProjectEditsEffects } from "../files/_ngrx/project-edits/project-edits.effects";
import { ReleaseEffects } from "../files/_ngrx/release/release.effects";
import { SearchEffects } from "../files/_ngrx/search/search.effects";
import { SystemEffects } from "../system/_ngrx/system.effects";
import { TableEffects } from "../files/_ngrx/table/table.effects";
import { TerraEffects } from "../files/_ngrx/terra/terra.effects";
Expand All @@ -32,6 +33,7 @@ export const AppEffects = [
ProjectEffects,
ProjectEditsEffects,
ReleaseEffects,
SearchEffects,
SystemEffects,
TableEffects,
TerraEffects,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export class AnalysisProtocolEffects {
withLatestFrom(this.store.pipe(select(selectPreviousQuery), take(1)))
)),
tap(([action, queryWhenActionTriggered]) => {
this.gtmService.trackEvent((action as ViewAnalysisProtocolAction).asEvent(queryWhenActionTriggered));
this.gtmService.trackEvent((action as ViewAnalysisProtocolAction).asEvent({
currentQuery: queryWhenActionTriggered
}));
})
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ export class ViewAnalysisProtocolAction implements Action, TrackingAction {
/**
* Return the clear action as a GA event.
*
* @param {string} currentQuery
* @param {{[key: string]: any}} dimensions
* @returns {GAEvent}
*/
public asEvent(currentQuery: string): GAEvent {
public asEvent({currentQuery}): GAEvent {

return {
category: GACategory.PORTAL_LINK,
Expand Down
3 changes: 2 additions & 1 deletion spa/src/app/files/_ngrx/analytics/tracking.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export interface TrackingAction extends Action {
/**
* Return action in the format of a GA event.
*
* @param {{[key: string]: any}} dimensions
* @returns {string}
*/
asEvent(currentQuery?: string): GAEvent;
asEvent(dimensions?: {[key: string]: any}): GAEvent;
}
7 changes: 5 additions & 2 deletions spa/src/app/files/_ngrx/entity/entity.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import { concatMap, withLatestFrom } from "rxjs/operators";
// App dependencies
import { AppState } from "../../../_ngrx/app.state";
import { SelectEntityAction } from "./select-entity.action";
import { InitEntityStateAction, NoOpAction } from "../facet/file-facet-list.actions";
import { selectTableQueryParams } from "../file.selectors";
import { GTMService } from "../../../shared/analytics/gtm.service";
import { getSelectedTable } from "../table/table.state";
import { BackToEntityAction } from "./back-to-entity.action";
import { selectPreviousQuery } from "../search/search.selectors";
import { InitEntityStateAction } from "./init-entity-state.action";
import { NoOpAction } from "../facet/no-op.action";

@Injectable()
export class EntityEffects {
Expand Down Expand Up @@ -55,7 +56,9 @@ export class EntityEffects {
map(([action, tableQueryParams, queryWhenActionTriggered]) => {

// Track change of tab
this.gtmService.trackEvent((action as SelectEntityAction).asEvent(queryWhenActionTriggered));
this.gtmService.trackEvent((action as SelectEntityAction).asEvent({
currentQuery: queryWhenActionTriggered
}));

// Return cached table, if available
if ( getSelectedTable(tableQueryParams.tableState).data.length ) {
Expand Down
15 changes: 15 additions & 0 deletions spa/src/app/files/_ngrx/entity/init-entity-state.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Action dispatched when entity state is not yet cached and needs to be initialized.
*/

// App dependencies
import { Action } from "@ngrx/store";

export class InitEntityStateAction implements Action {

public static ACTION_TYPE = "FILE.FACET.INIT_ENTITY_STATE";
public readonly type = InitEntityStateAction.ACTION_TYPE;
}
6 changes: 3 additions & 3 deletions spa/src/app/files/_ngrx/entity/select-entity.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ export class SelectEntityAction implements Action, TrackingAction {
/**
* Return the cleared age range action as a GA event.
*
* @param {string} currentQuery
* @param {{[key: string]: any}} dimensions
* @returns {GAEvent}
*/
public asEvent(currentQuery: string): GAEvent {
public asEvent({currentQuery}): GAEvent {

return {
category: GACategory.ENTITY,
action: this.getTrackingAction(),
label: this.entityKey,
label: this.entityKey.charAt(0).toUpperCase() + this.entityKey.slice(1), // Capitalize first letter of entity
dimensions: {
[GADimension.CURRENT_QUERY]: currentQuery,
}
Expand Down
4 changes: 1 addition & 3 deletions spa/src/app/files/_ngrx/facet/facet.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import { Action } from "@ngrx/store";

// App dependencies
import { ClearIsMatrixSupportedAction } from "./clear-is-matrix-supported.action";
import { InitEntityStateAction } from "../entity/init-entity-state.action";
import { FetchFacetsSuccessAction } from "./fetch-facets-success-action.action";
import { FetchIsMatrixSupportedRequestAction } from "./fetch-is-matrix-supported-request.action";
import { FetchIsMatrixSupportedSuccessAction } from "./fetch-is-matrix-supported-success.action";
import { FacetState } from "./facet.state";
import {
InitEntityStateAction
} from "./file-facet-list.actions";
import { SelectFileFacetTermAction } from "../search/select-file-facet-term.action";
import { ClearSelectedTermsAction } from "../search/clear-selected-terms.action";
import { SetViewStateAction } from "./set-view-state.action";
Expand Down
22 changes: 22 additions & 0 deletions spa/src/app/files/_ngrx/facet/fetch-file-facets-request.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Action dispatched when file fileFacets are to be updated. This can be on load of app, select or clear of fileFacets, or
* select of project.
*/

// Core dependencies
import { Action } from "@ngrx/store";

export class FetchFileFacetsRequestAction implements Action {

public static ACTION_TYPE = "FILE.FACET.FETCH_REQUEST";
public readonly type = FetchFileFacetsRequestAction.ACTION_TYPE;

/**
* @param {boolean} updateTableData - True if table data is to be updated. This is false on select of project as we
* want table data and state (eg pagination) to remain unchanged on select of project.
*/
constructor(public readonly updateTableData: boolean) {}
}
44 changes: 0 additions & 44 deletions spa/src/app/files/_ngrx/facet/file-facet-list.actions.ts

This file was deleted.

14 changes: 14 additions & 0 deletions spa/src/app/files/_ngrx/facet/no-op.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Human Cell Atlas
* https://www.humancellatlas.org/
*
* Noop action.
*/

// Core dependencies
import { Action } from "@ngrx/store";

export class NoOpAction implements Action {
public static ACTION_TYPE = "FILE.FACET.NOOP";
public readonly type = NoOpAction.ACTION_TYPE;
}
8 changes: 7 additions & 1 deletion spa/src/app/files/_ngrx/facet/set-view-state.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@

// Core dependencies
import { Action } from "@ngrx/store";
import { QueryStringSearchTerm } from "../../search/url/query-string-search-term.model";

// App dependencies
import { QueryStringSearchTerm } from "../../search/url/query-string-search-term.model";

export class SetViewStateAction implements Action {

public static ACTION_TYPE = "FILE.FACET.SET_VIEW_STATE";
public readonly type = SetViewStateAction.ACTION_TYPE;

/**
* @param {string} selectedEntity
* @param {QueryStringSearchTerm[]} selectedSearchTerms
*/
constructor(public selectedEntity: string, public selectedSearchTerms: QueryStringSearchTerm[]) {}
}
12 changes: 6 additions & 6 deletions spa/src/app/files/_ngrx/file.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { MockStore, provideMockStore } from "@ngrx/store/testing";
import { Observable, of } from "rxjs";

// App dependencies
import { FetchFacetsSuccessAction } from "./facet/fetch-facets-success-action.action";
import { FetchFileFacetsRequestAction } from "./facet/fetch-file-facets-request.action";
import { FileEffects } from "./file.effects";
import { FileState } from "./file.state";
import {
Expand All @@ -22,8 +24,6 @@ import {
DEFAULT_SAMPLES_STATE, FILES_STATE_WITH_SEARCH_TERM,
PROJECTS_STATE_WITH_PROJECT_SEARCH_TERM, SAMPLES_STATE_WITH_SEARCH_TERM
} from "./file.state.mock";
import { FetchFileFacetsRequestAction } from "./facet/file-facet-list.actions";
import { FetchFacetsSuccessAction } from "./facet/fetch-facets-success-action.action";
import { SearchTermsUpdatedAction } from "./search/search-terms-updated.action";
import { GTMService } from "../../shared/analytics/gtm.service";
import { DEFAULT_PROJECTS_ENTITY_SEARCH_RESULTS } from "../shared/entity-search-results.mock";
Expand Down Expand Up @@ -84,7 +84,7 @@ describe("File Effects", () => {
actions = hot("--a-", {
a: new FetchFileFacetsRequestAction(false)
});

const expected = cold("--(bcd)", {
b: new FetchFacetsSuccessAction(DEFAULT_PROJECTS_ENTITY_SEARCH_RESULTS.facets),
c: new SearchTermsUpdatedAction([]),
Expand Down Expand Up @@ -114,15 +114,15 @@ describe("File Effects", () => {

expect(effects.fetchFacets$).toBeObservable(expected);
});

/**
* A new request to fetch table data should be returned when viewing the projects tab and there is currently a
* selected project.
*/
it("fetchFacets$ - projects tab - should request table data when a project search term is selected", () => {

// Update search state to include a selected project search term
store.setState(PROJECTS_STATE_WITH_PROJECT_SEARCH_TERM);
store.setState(PROJECTS_STATE_WITH_PROJECT_SEARCH_TERM);

// Dispatch the fetch file facets action
actions = hot("--a-", {
Expand All @@ -137,7 +137,7 @@ describe("File Effects", () => {

expect(effects.fetchFacets$).toBeObservable(expected);
});

/**
* Full table model (data, pagination and term counts) should be updated when fetching facets if the "update table
* data" flag is set to true and the current tab is samples.
Expand Down
59 changes: 35 additions & 24 deletions spa/src/app/files/_ngrx/file.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,31 @@ import { Observable, of } from "rxjs";
import { concatMap, map, mergeMap, switchMap, take, withLatestFrom } from "rxjs/operators";

// App dependencies
import { TrackingAction } from "./analytics/tracking.action";
import { InitEntityStateAction } from "./entity/init-entity-state.action";
import { FetchFacetsSuccessAction } from "./facet/fetch-facets-success-action.action";
import { FetchFileFacetsRequestAction } from "./facet/fetch-file-facets-request.action";
import { FetchIsMatrixSupportedRequestAction } from "./facet/fetch-is-matrix-supported-request.action";
import { FetchIsMatrixSupportedSuccessAction } from "./facet/fetch-is-matrix-supported-success.action";
import { FileFacetName } from "../facet/file-facet/file-facet-name.model";
import { SetViewStateAction } from "./facet/set-view-state.action";
import { FetchFileFacetsRequestAction, InitEntityStateAction, NoOpAction } from "./facet/file-facet-list.actions";
import { selectTableQueryParams } from "./file.selectors";
import { FileSummary } from "../file-summary/file-summary";
import { FetchFileSummaryRequestAction, FetchFileSummarySuccessAction } from "./file-summary/file-summary.actions";
import { AppState } from "../../_ngrx/app.state";
import { ClearSelectedTermsAction } from "./search/clear-selected-terms.action";
import { SelectFileFacetTermAction } from "./search/select-file-facet-term.action";
import { selectPreviousQuery, selectSelectedSearchTerms } from "./search/search.selectors";
import { selectCurrentQuery, selectSelectedSearchTerms } from "./search/search.selectors";
import { SearchTermsUpdatedAction } from "./search/search-terms-updated.action";
import { SearchTerm } from "../search/search-term.model";
import { SelectFacetAgeRangeAction } from "./search/select-facet-age-range.action";
import { ClearSelectedAgeRangeAction } from "./search/clear-selected-age-range.action";
import { GTMService } from "../../shared/analytics/gtm.service";
import { EntityName } from "../shared/entity-name.model";
import { FilesService } from "../shared/files.service";
import { GAAction } from "../../shared/analytics/ga-action.model";
import { GACategory } from "../../shared/analytics/ga-category.model";
import { GADimension } from "../../shared/analytics/ga-dimension.model";
import { GAIndex } from "../../shared/analytics/ga-index.model";
import { FetchTableDataRequestAction } from "./table/fetch-table-data-request.action";
import { FetchTableModelSuccessAction } from "./table/fetch-table-model-success.action";
import { DEFAULT_TABLE_PARAMS } from "../table/pagination/table-params.model";
Expand Down Expand Up @@ -72,15 +76,7 @@ export class FileEffects {
SelectFileFacetTermAction.ACTION_TYPE, // Selecting facet term eg file type "matrix"
SelectFacetAgeRangeAction.ACTION_TYPE // Setting age range
),
concatMap(action => of(action).pipe(
withLatestFrom(this.store.pipe(select(selectPreviousQuery), take(1)))
)),
mergeMap(([action, queryWhenActionTriggered]) => {

// If this action is a tracking action, send tracking event.
if ( (action as TrackingAction).asEvent ) {
this.gtmService.trackEvent((action as TrackingAction).asEvent(queryWhenActionTriggered));
}
mergeMap(() => {

// Return an array of actions that need to be dispatched - we need to (re-)request summary and facet
// (including table) data.
Expand All @@ -95,22 +91,20 @@ export class FileEffects {

/**
* Fetch data to populate facet menus, facet summary and potentially table data. If we are currently on the projects
* tab with a selected project, an additional call to populate the table is called.
* tab with a selected project, an additional call to populate the table is called. Track any cases where the result
* set is empty.
*/
@Effect()
fetchFacets$: Observable<Action> = this.actions$
.pipe(
ofType(FetchFileFacetsRequestAction.ACTION_TYPE),
switchMap((action) =>
this.store.pipe(
select(selectTableQueryParams),
take(1),
map((tableQueryParams) => {
return {action, tableQueryParams};
})
concatMap(action => of(action).pipe(
withLatestFrom(
this.store.pipe(select(selectTableQueryParams), take(1)),
this.store.pipe(select(selectCurrentQuery), take(1))
)
),
switchMap(({action, tableQueryParams}) => {
)),
switchMap(([action, tableQueryParams, currentQuery]) => {

const selectedSearchTermsBySearchKey = tableQueryParams.selectedSearchTermsBySearchKey;
const selectedEntity = tableQueryParams.tableState.selectedEntity;
Expand All @@ -125,11 +119,28 @@ export class FileEffects {
return this.fileService.fetchEntitySearchResults(selectedSearchTermsBySearchKey, tableParams, selectedEntity)
.pipe(
map((entitySearchResults) => {
return {action, entitySearchResults, tableQueryParams};
return {action, entitySearchResults, currentQuery, tableQueryParams};
})
);
}),
mergeMap(({action, tableQueryParams, entitySearchResults}) => {
mergeMap(({action, entitySearchResults, currentQuery, tableQueryParams}) => {

// Track empty search results, using the tracking event triggered from the original action as a base
const emptyResultSet = entitySearchResults.tableModel.data.length === 0;
if ( emptyResultSet ) {

const selectedEntity = tableQueryParams.tableState.selectedEntity;
const index = GAIndex[selectedEntity.toUpperCase()];
this.gtmService.trackEvent({
category: GACategory.SEARCH,
action: GAAction.EXCEPTION,
label: "Empty Result Set",
dimensions: {
[GADimension.CURRENT_QUERY]: currentQuery,
[GADimension.INDEX]: index
}
});
}

// Set up fetch success action
const fetchSuccessAction = new FetchFacetsSuccessAction(entitySearchResults.facets);
Expand Down
Loading

0 comments on commit b9c5f91

Please sign in to comment.