Skip to content

Commit 465d888

Browse files
Added tracking of facet de/select. Resolves #1162. (#1184)
1 parent 2c082c1 commit 465d888

File tree

56 files changed

+800
-396
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+800
-396
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Human Cell Atlas
3+
* https://www.humancellatlas.org/
4+
*
5+
* Model of action triggered when action is to be tracked by GA/GTM.
6+
*/
7+
8+
// Core dependencies
9+
import { Action } from "@ngrx/store";
10+
11+
// App dependencies
12+
import { GAEvent } from "../../../shared/analytics/ga-event.model";
13+
import { GASource } from "../../../shared/analytics/ga-source.model";
14+
15+
export interface TrackingAction extends Action {
16+
17+
source: GASource; // Element/component where tracking event originated
18+
currentQuery: string; // Current set of selected search terms
19+
20+
/**
21+
* Return action in the format of a GA event.
22+
*
23+
* @returns {string}
24+
*/
25+
asEvent(): GAEvent;
26+
}

spa/src/app/files/_ngrx/file.effects.spec.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ import {
2323
PROJECTS_STATE_WITH_PROJECT_SEARCH_TERM, SAMPLES_STATE_WITH_SEARCH_TERM
2424
} from "./file.state.mock";
2525
import { FetchFileFacetsRequestAction } from "./facet/file-facet-list.actions";
26-
import { SearchTermsUpdatedAction } from "./search/search-terms-updated-action.action";
26+
import { FetchFacetsSuccessAction } from "./facet/fetch-facets-success-action.action";
27+
import { SearchTermsUpdatedAction } from "./search/search-terms-updated.action";
28+
import { GTMService } from "../../shared/analytics/gtm.service";
2729
import { DEFAULT_PROJECTS_ENTITY_SEARCH_RESULTS } from "../shared/entity-search-results.mock";
2830
import { FilesService } from "../shared/files.service";
2931
import { DEFAULT_FILE_SUMMARY } from "../shared/file-summary.mock";
3032
import { TermCountsUpdatedAction } from "./table/term-counts-updated.action";
3133
import { FetchTableModelSuccessAction } from "./table/fetch-table-model-success.action";
3234
import { FetchTableDataRequestAction } from "./table/fetch-table-data-request.action";
33-
import { FetchFacetsSuccessAction } from "./facet/fetch-facets-success-action.action";
3435

3536
describe("File Effects", () => {
3637

@@ -58,7 +59,13 @@ describe("File Effects", () => {
5859
provide: FilesService,
5960
useValue: filesService
6061
},
61-
provideMockStore({initialState: DEFAULT_PROJECTS_STATE})
62+
provideMockStore({initialState: DEFAULT_PROJECTS_STATE}),
63+
{
64+
provide: GTMService,
65+
useValue: jasmine.createSpyObj("GTMService", [
66+
"trackEvent"
67+
])
68+
}
6269
]
6370
});
6471

spa/src/app/files/_ngrx/file.effects.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,33 @@ import { Observable, of } from "rxjs";
1313
import { map, mergeMap, switchMap, take } from "rxjs/operators";
1414

1515
// App dependencies
16+
import { TrackingAction } from "./analytics/tracking.action";
17+
import { FetchFacetsSuccessAction } from "./facet/fetch-facets-success-action.action";
1618
import { FetchIsMatrixSupportedRequestAction } from "./facet/fetch-is-matrix-supported-request.action";
1719
import { FetchIsMatrixSupportedSuccessAction } from "./facet/fetch-is-matrix-supported-success.action";
20+
import { FileFacetName } from "../facet/file-facet/file-facet-name.model";
1821
import { SetViewStateAction } from "./facet/set-view-state.action";
22+
import { FetchFileFacetsRequestAction, InitEntityStateAction, NoOpAction } from "./facet/file-facet-list.actions";
23+
import { selectTableQueryParams } from "./file.selectors";
1924
import { FileSummary } from "../file-summary/file-summary";
20-
import {
21-
FetchFileFacetsRequestAction,
22-
InitEntityStateAction,
23-
NoOpAction
24-
} from "./facet/file-facet-list.actions";
25-
import {
26-
FetchFileSummaryRequestAction,
27-
FetchFileSummarySuccessAction
28-
} from "./file-summary/file-summary.actions";
29-
import {
30-
selectTableQueryParams
31-
} from "app/files/_ngrx/file.selectors";
25+
import { FetchFileSummaryRequestAction, FetchFileSummarySuccessAction } from "./file-summary/file-summary.actions";
3226
import { AppState } from "../../_ngrx/app.state";
3327
import { ClearSelectedTermsAction } from "./search/clear-selected-terms.action";
3428
import { SelectFileFacetTermAction } from "./search/select-file-facet-term.action";
35-
import { SearchTerm } from "../search/search-term.model";
3629
import { selectSelectedSearchTerms } from "./search/search.selectors";
37-
import { SearchTermsUpdatedAction } from "./search/search-terms-updated-action.action";
30+
import { SearchTermsUpdatedAction } from "./search/search-terms-updated.action";
31+
import { SearchTerm } from "../search/search-term.model";
32+
import { SelectFacetAgeRangeAction } from "./search/select-facet-age-range.action";
33+
import { ClearSelectedAgeRangeAction } from "./search/clear-selected-age-range.action";
34+
import { GTMService } from "../../shared/analytics/gtm.service";
3835
import { EntityName } from "../shared/entity-name.model";
39-
import { FileFacetName } from "../facet/file-facet/file-facet-name.model";
4036
import { FilesService } from "../shared/files.service";
4137
import { FetchTableDataRequestAction } from "./table/fetch-table-data-request.action";
4238
import { FetchTableModelSuccessAction } from "./table/fetch-table-model-success.action";
4339
import { EntitySelectAction } from "./table/table.actions";
4440
import { DEFAULT_TABLE_PARAMS } from "../table/table-params.model";
4541
import { getSelectedTable } from "./table/table.state";
4642
import { TermCountsUpdatedAction } from "./table/term-counts-updated.action";
47-
import { FetchFacetsSuccessAction } from "./facet/fetch-facets-success-action.action";
48-
import { SelectFacetAgeRangeAction } from "./search/select-facet-age-range.action";
49-
import { ClearSelectedAgeRangeAction } from "./search/clear-selected-age-range.action";
5043

5144
@Injectable()
5245
export class FileEffects {
@@ -55,10 +48,12 @@ export class FileEffects {
5548
* @param {Store<AppState>} store
5649
* @param {Actions} actions$
5750
* @param {FilesService} fileService
51+
* @param {GTMService} gtmService
5852
*/
5953
constructor(private store: Store<AppState>,
6054
private actions$: Actions,
61-
private fileService: FilesService) {
55+
private fileService: FilesService,
56+
private gtmService: GTMService) {
6257
}
6358

6459
/**
@@ -79,7 +74,12 @@ export class FileEffects {
7974
SelectFileFacetTermAction.ACTION_TYPE, // Selecting facet term eg file type "matrix"
8075
SelectFacetAgeRangeAction.ACTION_TYPE // Setting age range
8176
),
82-
mergeMap(() => {
77+
mergeMap((action) => {
78+
79+
// If this action is a tracking action, send tracking event.
80+
if ( (action as TrackingAction).asEvent ) {
81+
this.gtmService.trackEvent((action as TrackingAction).asEvent());
82+
}
8383

8484
// Return an array of actions that need to be dispatched - we need to (re-)request summary and facet
8585
// (including table) data.

spa/src/app/files/_ngrx/search/clear-selected-age-range.action.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,53 @@ import { Action } from "@ngrx/store";
1212
import { AgeRange } from "../../facet/facet-age-range/age-range.model";
1313
import { SearchAgeRange } from "../../search/search-age-range.model";
1414
import { SearchTermAction } from "./search-term.action";
15+
import { TrackingAction } from "../analytics/tracking.action";
16+
import { GACategory } from "../../../shared/analytics/ga-category.model";
17+
import { GAAction } from "../../../shared/analytics/ga-action.model";
18+
import { GAEvent } from "../../../shared/analytics/ga-event.model";
19+
import { GAEntityType } from "../../../shared/analytics/ga-entity-type.model";
20+
import { GADimension } from "../../../shared/analytics/ga-dimension.model";
21+
import { GASource } from "../../../shared/analytics/ga-source.model";
1522

16-
export class ClearSelectedAgeRangeAction implements Action, SearchTermAction {
23+
export class ClearSelectedAgeRangeAction implements Action, SearchTermAction, TrackingAction {
1724

1825
public static ACTION_TYPE = "FILE.SEARCH.CLEAR_SELECTED_AGE_RANGE";
1926
public readonly type = ClearSelectedAgeRangeAction.ACTION_TYPE;
2027

2128
/**
2229
* @param {string} facetName
2330
* @param {AgeRange} ageRange
31+
* @param {GASource} source
32+
* @param {string} currentQuery
2433
*/
2534
constructor(public readonly facetName: string,
26-
public readonly ageRange: AgeRange) {}
35+
public readonly ageRange: AgeRange,
36+
public source: GASource,
37+
public currentQuery: string) {}
38+
39+
/**
40+
* Return the cleared age range action as a GA event.
41+
*
42+
* @returns {GAEvent}
43+
*/
44+
public asEvent(): GAEvent {
45+
46+
const term = this.asSearchTerm().getDisplayValue();
47+
return {
48+
category: GACategory.SEARCH,
49+
action: GAAction.DESELECT, // Always DESELECT for this action (see SelectSelectedAgeRangeAction for SELECT)
50+
label: term,
51+
dimensions: {
52+
[GADimension.CURRENT_QUERY]: this.currentQuery,
53+
[GADimension.ENTITY_TYPE]: GAEntityType.FACET,
54+
[GADimension.FACET]: this.facetName,
55+
[GADimension.MAX]: `${this.ageRange.ageMax}`,
56+
[GADimension.MIN]: `${this.ageRange.ageMin}`,
57+
[GADimension.SOURCE]: this.source,
58+
[GADimension.TERM]: term
59+
}
60+
};
61+
}
2762

2863
/**
2964
* Returns selected file facet term in search term format.

spa/src/app/files/_ngrx/search/clear-selected-terms.action.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,48 @@
22
* Human Cell Atlas
33
* https://www.humancellatlas.org/
44
*
5-
* Action that is triggered when all selected file facet terms are to be removed and default state is restored.
5+
* Action that is triggered when all selected facet terms are to be removed and default state is restored.
66
*/
77

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

11-
export class ClearSelectedTermsAction implements Action {
11+
// App dependencies
12+
import { TrackingAction } from "../analytics/tracking.action";
13+
import { GACategory } from "../../../shared/analytics/ga-category.model";
14+
import { GAAction } from "../../../shared/analytics/ga-action.model";
15+
import { GAEvent } from "../../../shared/analytics/ga-event.model";
16+
import { GAEntityType } from "../../../shared/analytics/ga-entity-type.model";
17+
import { GADimension } from "../../../shared/analytics/ga-dimension.model";
18+
import { GASource } from "../../../shared/analytics/ga-source.model";
19+
20+
export class ClearSelectedTermsAction implements Action, TrackingAction {
21+
1222
public static ACTION_TYPE = "FILE.SEARCH.CLEAR_SELECTED_TERMS";
1323
public readonly type = ClearSelectedTermsAction.ACTION_TYPE;
14-
constructor() {}
24+
25+
/**
26+
* @param {GASource} source
27+
* @param {string} currentQuery
28+
*/
29+
constructor(public source: GASource, public currentQuery: string) {}
30+
31+
/**
32+
* Return the clear action as a GA event.
33+
*
34+
* @returns {GAEvent}
35+
*/
36+
public asEvent(): GAEvent {
37+
38+
return {
39+
category: GACategory.SEARCH,
40+
action: GAAction.CLEAR,
41+
label: "Clear All",
42+
dimensions: {
43+
[GADimension.CURRENT_QUERY]: this.currentQuery,
44+
[GADimension.ENTITY_TYPE]: GAEntityType.FACET,
45+
[GADimension.SOURCE]: this.source
46+
}
47+
};
48+
}
1549
}

spa/src/app/files/_ngrx/search/search.reducer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { SearchState } from "./search.state";
1313
import { SelectFileFacetTermAction } from "./select-file-facet-term.action";
1414
import { ClearSelectedTermsAction } from "./clear-selected-terms.action";
1515
import { SetViewStateAction } from "../facet/set-view-state.action";
16-
import { SearchTermsUpdatedAction } from "./search-terms-updated-action.action";
16+
import { SearchTermsUpdatedAction } from "./search-terms-updated.action";
1717
import { SelectProjectIdAction } from "./select-project-id.action";
1818
import { SelectFacetAgeRangeAction } from "./select-facet-age-range.action";
1919
import { SelectSearchTermAction } from "./select-search-term.action";

spa/src/app/files/_ngrx/search/search.state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { SearchFacetTerm } from "../../search/search-facet-term.model";
1717
import { SearchTerm } from "../../search/search-term.model";
1818
import { SelectSearchTermAction } from "./select-search-term.action";
1919
import { SearchEntity } from "../../search/search-entity.model";
20-
import { SearchTermsUpdatedAction } from "./search-terms-updated-action.action";
20+
import { SearchTermsUpdatedAction } from "./search-terms-updated.action";
2121
import { QueryStringSearchTerm } from "../../search/url/query-string-search-term.model";
2222
import { FacetAgeRangeName } from "../../facet/facet-age-range/facet-age-range-name.model";
2323
import { SearchAgeRange } from "../../search/search-age-range.model";

spa/src/app/files/_ngrx/search/select-facet-age-range.action.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,53 @@ import { AgeRange } from "../../facet/facet-age-range/age-range.model";
1414
import { SearchAgeRange } from "../../search/search-age-range.model";
1515
import { SearchTermAction } from "./search-term.action";
1616
import { SearchTerm } from "../../search/search-term.model";
17+
import { TrackingAction } from "../analytics/tracking.action";
18+
import { GACategory } from "../../../shared/analytics/ga-category.model";
19+
import { GAAction } from "../../../shared/analytics/ga-action.model";
20+
import { GAEvent } from "../../../shared/analytics/ga-event.model";
21+
import { GAEntityType } from "../../../shared/analytics/ga-entity-type.model";
22+
import { GADimension } from "../../../shared/analytics/ga-dimension.model";
23+
import { GASource } from "../../../shared/analytics/ga-source.model";
1724

18-
export class SelectFacetAgeRangeAction implements Action, SearchTermAction {
25+
export class SelectFacetAgeRangeAction implements Action, SearchTermAction, TrackingAction {
1926

2027
public static ACTION_TYPE = "FILE.SEARCH.SELECT_AGE_RANGE";
2128
public readonly type = SelectFacetAgeRangeAction.ACTION_TYPE;
2229

2330
/**
2431
* @param {string} facetName
2532
* @param {AgeRange} ageRange
33+
* @param {GASource} source
34+
* @param {string} currentQuery
2635
*/
2736
constructor(public readonly facetName: string,
28-
public readonly ageRange: AgeRange) {}
37+
public readonly ageRange: AgeRange,
38+
public source: GASource,
39+
public currentQuery: string) {}
40+
41+
/**
42+
* Return the de/selected term as a GA event.
43+
*
44+
* @returns {GAEvent}
45+
*/
46+
public asEvent(): GAEvent {
47+
48+
const term = this.asSearchTerm().getDisplayValue();
49+
return {
50+
category: GACategory.SEARCH,
51+
action: GAAction.SELECT, // Always SELECT for this action (see ClearSelectedAgeRangeAction for DESELECT
52+
label: term,
53+
dimensions: {
54+
[GADimension.CURRENT_QUERY]: this.currentQuery,
55+
[GADimension.ENTITY_TYPE]: GAEntityType.FACET,
56+
[GADimension.FACET]: this.facetName,
57+
[GADimension.MAX]: `${this.ageRange.ageMax}`,
58+
[GADimension.MIN]: `${this.ageRange.ageMin}`,
59+
[GADimension.SOURCE]: this.source,
60+
[GADimension.TERM]: term
61+
}
62+
};
63+
}
2964

3065
/**
3166
* Returns selected file facet term in search term format.

spa/src/app/files/_ngrx/search/select-file-facet-term.action.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@
1010
import { Action } from "@ngrx/store";
1111

1212
// App dependencies
13+
import { TrackingAction } from "../analytics/tracking.action";
1314
import { SearchFacetTerm } from "../../search/search-facet-term.model";
1415
import { SearchTerm } from "../../search/search-term.model";
1516
import { SelectSearchTermAction } from "./select-search-term.action";
17+
import { GAEvent } from "../../../shared/analytics/ga-event.model";
18+
import { GAAction } from "../../../shared/analytics/ga-action.model";
19+
import { GACategory } from "../../../shared/analytics/ga-category.model";
20+
import { GADimension } from "../../../shared/analytics/ga-dimension.model";
21+
import { GAEntityType } from "../../../shared/analytics/ga-entity-type.model";
22+
import { GASource } from "../../../shared/analytics/ga-source.model";
1623

17-
export class SelectFileFacetTermAction implements Action, SelectSearchTermAction {
24+
export class SelectFileFacetTermAction implements Action, SelectSearchTermAction, TrackingAction {
1825

1926
public static ACTION_TYPE = "FILE.SEARCH.SELECT_FACET_TERM";
2027
public readonly type = SelectFileFacetTermAction.ACTION_TYPE;
@@ -23,10 +30,35 @@ export class SelectFileFacetTermAction implements Action, SelectSearchTermAction
2330
* @param {string} facetName
2431
* @param {string} termName
2532
* @param {boolean} selected
33+
* @param {GASource} source
34+
* @param {string} currentQuery
2635
*/
2736
constructor(public readonly facetName: string,
2837
public readonly termName: string,
29-
public readonly selected = true) {}
38+
public readonly selected = true,
39+
public source: GASource,
40+
public currentQuery: string) {}
41+
42+
/**
43+
* Return the selected age range as a GA event.
44+
*
45+
* @returns {GAEvent}
46+
*/
47+
public asEvent(): GAEvent {
48+
49+
return {
50+
category: GACategory.SEARCH,
51+
action: this.selected ? GAAction.SELECT : GAAction.DESELECT,
52+
label: this.termName,
53+
dimensions: {
54+
[GADimension.CURRENT_QUERY]: this.currentQuery,
55+
[GADimension.ENTITY_TYPE]: GAEntityType.FACET,
56+
[GADimension.FACET]: this.facetName,
57+
[GADimension.SOURCE]: this.source,
58+
[GADimension.TERM]: this.termName
59+
}
60+
};
61+
}
3062

3163
/**
3264
* Returns selected file facet term in search term format.

0 commit comments

Comments
 (0)