Skip to content

Commit 7043b0b

Browse files
Added tracking of tab selection. Resolves #1201. (#1221)
1 parent 49acba5 commit 7043b0b

22 files changed

+260
-105
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// App dependencies
99
import { ConfigEffects } from "../config/_ngrx/config.effects";
1010
import { AnalysisProtocolEffects } from "../files/_ngrx/analysis-protocol/analysis-protocol.effects";
11+
import { EntityEffects } from "../files/_ngrx/entity/entity.effects";
1112
import { FileEffects } from "../files/_ngrx/file.effects";
1213
import { FileManifestEffects } from "../files/_ngrx/file-manifest/file-manifest.effects";
1314
import { IntegrationEffects } from "../files/_ngrx/integration/integration.effects";
@@ -23,6 +24,7 @@ import { UrlEffects } from "../files/_ngrx/url/url.effects";
2324
export const AppEffects = [
2425
AnalysisProtocolEffects,
2526
ConfigEffects,
27+
EntityEffects,
2628
FileEffects,
2729
FileManifestEffects,
2830
IntegrationEffects,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Human Cell Atlas
3+
* https://www.humancellatlas.org/
4+
*
5+
* Action dispatched when the "return to selected entity" back buttons are selected (eg from project detail or get data
6+
* flow).
7+
*/
8+
9+
// App dependencies
10+
import { GAAction } from "../../../shared/analytics/ga-action.model";
11+
import { SelectEntityAction } from "./select-entity.action";
12+
13+
export class BackToEntityAction extends SelectEntityAction {
14+
15+
public static ACTION_TYPE = "ENTITY.BACK";
16+
public readonly type = BackToEntityAction.ACTION_TYPE;
17+
18+
/**
19+
* @param {string} entityKey
20+
* @param {string} currentQuery
21+
*/
22+
constructor(public entityKey: string,
23+
public currentQuery: string) {
24+
super(entityKey, currentQuery);
25+
}
26+
27+
/**
28+
* Return the tracking event action for this action.
29+
*
30+
* @returns {GAAction}
31+
*/
32+
protected getTrackingAction(): GAAction {
33+
return GAAction.RETURN_TO_TAB;
34+
}
35+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Human Cell Atlas
3+
* https://www.humancellatlas.org/
4+
*
5+
* Entity-related effects. An entity is currently one of Projects, Samples or Files.
6+
*/
7+
8+
// Core dependencies
9+
import { Injectable } from "@angular/core";
10+
import { Actions, Effect, ofType } from "@ngrx/effects";
11+
import { Action, select, Store } from "@ngrx/store";
12+
import { Observable, of } from "rxjs";
13+
import { map, take } from "rxjs/operators";
14+
import { concatMap, withLatestFrom } from "rxjs/operators";
15+
16+
// App dependencies
17+
import { AppState } from "../../../_ngrx/app.state";
18+
import { SelectEntityAction } from "./select-entity.action";
19+
import { InitEntityStateAction, NoOpAction } from "../facet/file-facet-list.actions";
20+
import { selectTableQueryParams } from "../file.selectors";
21+
import { GTMService } from "../../../shared/analytics/gtm.service";
22+
import { getSelectedTable } from "../table/table.state";
23+
import { BackToEntityAction } from "./back-to-entity.action";
24+
25+
@Injectable()
26+
export class EntityEffects {
27+
28+
/**
29+
* @param {Store<AppState>} store
30+
* @param {Actions} actions$
31+
* @param {GTMService} gtmService
32+
*/
33+
constructor(private store: Store<AppState>,
34+
private actions$: Actions,
35+
private gtmService: GTMService) {
36+
}
37+
38+
/**
39+
* Handle action where tab is selected (ie Projects, Samples, Files).
40+
*/
41+
@Effect()
42+
switchTabs: Observable<Action> = this.actions$
43+
.pipe(
44+
ofType(
45+
SelectEntityAction.ACTION_TYPE,
46+
BackToEntityAction.ACTION_TYPE
47+
),
48+
concatMap(action => of(action).pipe(
49+
withLatestFrom(this.store.pipe(select(selectTableQueryParams), take(1)))
50+
)),
51+
map(([action, tableQueryParams]) => {
52+
53+
// Track change of tab
54+
this.gtmService.trackEvent((action as SelectEntityAction).asEvent());
55+
56+
// Return cached table, if available
57+
if ( getSelectedTable(tableQueryParams.tableState).data.length ) {
58+
return new NoOpAction();
59+
}
60+
61+
// Table data has not been previously loaded and is therefore not cached.
62+
return new InitEntityStateAction();
63+
})
64+
);
65+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Human Cell Atlas
3+
* https://www.humancellatlas.org/
4+
*
5+
* Action dispatched when tab is selected (eg Projects, Files or Samples).
6+
*/
7+
8+
// Core dependencies
9+
import { Action } from "@ngrx/store";
10+
11+
// App dependencies
12+
import { TrackingAction } from "../analytics/tracking.action";
13+
import { GAAction } from "../../../shared/analytics/ga-action.model";
14+
import { GACategory } from "../../../shared/analytics/ga-category.model";
15+
import { GADimension } from "../../../shared/analytics/ga-dimension.model";
16+
import { GAEvent } from "../../../shared/analytics/ga-event.model";
17+
18+
export class SelectEntityAction implements Action, TrackingAction {
19+
20+
public static ACTION_TYPE = "ENTITY.SELECT";
21+
public readonly type = SelectEntityAction.ACTION_TYPE;
22+
23+
/**
24+
* @param {string} entityKey
25+
* @param {string} currentQuery
26+
*/
27+
constructor(public entityKey: string,
28+
public currentQuery: string) {}
29+
30+
/**
31+
* Return the cleared age range action as a GA event.
32+
*
33+
* @returns {GAEvent}
34+
*/
35+
public asEvent(): GAEvent {
36+
37+
return {
38+
category: GACategory.ENTITY,
39+
action: this.getTrackingAction(),
40+
label: this.entityKey,
41+
dimensions: {
42+
[GADimension.CURRENT_QUERY]: this.currentQuery
43+
}
44+
};
45+
}
46+
47+
/**
48+
* Return the tracking event action for this action.
49+
*
50+
* @returns {GAAction}
51+
*/
52+
protected getTrackingAction(): GAAction {
53+
return GAAction.SELECT_TAB;
54+
}
55+
}

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

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ import { EntityName } from "../shared/entity-name.model";
3636
import { FilesService } from "../shared/files.service";
3737
import { FetchTableDataRequestAction } from "./table/fetch-table-data-request.action";
3838
import { FetchTableModelSuccessAction } from "./table/fetch-table-model-success.action";
39-
import { EntitySelectAction } from "./table/table.actions";
4039
import { DEFAULT_TABLE_PARAMS } from "../table/pagination/table-params.model";
41-
import { getSelectedTable } from "./table/table.state";
4240
import { TermCountsUpdatedAction } from "./table/term-counts-updated.action";
4341

4442
@Injectable()
@@ -210,29 +208,6 @@ export class FileEffects {
210208
map((fileSummary: FileSummary) => new FetchFileSummarySuccessAction(fileSummary))
211209
);
212210

213-
/**
214-
* Handle action where tab is selected (eg Samples or Files).
215-
*/
216-
@Effect()
217-
switchTabs: Observable<Action> = this.actions$
218-
.pipe(
219-
ofType(EntitySelectAction.ACTION_TYPE),
220-
switchMap(() => this.store.pipe(
221-
select(selectTableQueryParams),
222-
take(1)
223-
)),
224-
map((tableQueryParams) => {
225-
226-
// Return cached table, if available
227-
if ( getSelectedTable(tableQueryParams.tableState).data.length ) {
228-
return new NoOpAction();
229-
}
230-
231-
// Table data has not been previously loaded and is therefore not cached.
232-
return new InitEntityStateAction();
233-
})
234-
);
235-
236211
/**
237212
* Returns true if there is currently any projects in the current search terms.
238213
*

spa/src/app/files/_ngrx/table/table.actions.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,6 @@ export class TableOrderByAction implements Action {
1717
constructor(public field: string, public dir: string) {}
1818
}
1919

20-
/**
21-
* Action dispatched when tab is selected (eg Files or Samples).
22-
*/
23-
export class EntitySelectAction implements Action {
24-
public static ACTION_TYPE = "ENTITY.SELECT";
25-
public readonly type = EntitySelectAction.ACTION_TYPE;
26-
27-
constructor(public key: string) {}
28-
}
29-
3020
/**
3121
* Action dispatched when a project has been selected from the projects table and the corresponding project details are
3222
* to be requested from the server.
@@ -59,7 +49,6 @@ export class FetchProjectFailureAction implements Action {
5949
export type All
6050
= TableSetPageAction
6151
| TableOrderByAction
62-
| EntitySelectAction
6352
| FetchProjectRequestAction
6453
| FetchProjectSuccessAction
6554
| FetchProjectFailureAction;

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@ import { FetchTableModelSuccessAction } from "./fetch-table-model-success.action
1616
import { FetchTableDataSuccessAction } from "./fetch-table-data-success.action";
1717
import * as tableStateService from "./table.state";
1818
import { TableState } from "./table.state";
19-
import {
20-
EntitySelectAction,
21-
FetchProjectSuccessAction
22-
} from "./table.actions";
19+
import { FetchProjectSuccessAction } from "./table.actions";
2320
import { TableModel } from "../../table/table.model";
2421
import { TermCountsUpdatedAction } from "./term-counts-updated.action";
2522
import { TableNextPageSuccessAction } from "./table-next-page-success.action";
2623
import { TablePreviousPageSuccessAction } from "./table-previous-page-success.action";
24+
import { SelectEntityAction } from "../entity/select-entity.action";
2725

2826
export function reducer(state: TableState = tableStateService.getDefaultTableState(), action: Action): TableState {
2927

@@ -34,10 +32,10 @@ export function reducer(state: TableState = tableStateService.getDefaultTableSta
3432
switch (action.type) {
3533

3634
// User is switching tab, update selected entity.
37-
case EntitySelectAction.ACTION_TYPE:
35+
case SelectEntityAction.ACTION_TYPE:
3836

3937
nextState = {
40-
...state, selectedEntity: (action as EntitySelectAction).key
38+
...state, selectedEntity: (action as SelectEntityAction).entityKey
4139
};
4240

4341
return nextState;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ import { SelectFacetAgeRangeAction } from "../search/select-facet-age-range.acti
1919
import { ClearSelectedTermsAction } from "../search/clear-selected-terms.action";
2020
import { SelectFileFacetTermAction } from "../search/select-file-facet-term.action";
2121
import { selectUrlSpecState } from "./url.selectors";
22-
import { ActivatedRoute, Router } from "@angular/router";
22+
import { Router } from "@angular/router";
2323
import { EntityName } from "../../shared/entity-name.model";
2424
import { SearchTermUrlService } from "../../search/url/search-term-url.service";
25-
import { EntitySelectAction } from "../table/table.actions";
2625
import { SetViewStateAction } from "../facet/set-view-state.action";
26+
import { SelectEntityAction } from "../entity/select-entity.action";
2727

2828
@Injectable()
2929
export class UrlEffects {
@@ -50,7 +50,7 @@ export class UrlEffects {
5050
ofType(
5151
ClearSelectedTermsAction.ACTION_TYPE,
5252
ClearSelectedAgeRangeAction.ACTION_TYPE,
53-
EntitySelectAction.ACTION_TYPE,
53+
SelectEntityAction.ACTION_TYPE,
5454
SelectFileFacetTermAction.ACTION_TYPE,
5555
SelectFacetAgeRangeAction.ACTION_TYPE,
5656
SetViewStateAction.ACTION_TYPE

spa/src/app/files/files.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
[summary]="state.fileSummary">
1717
</hca-file-summary>
1818
<hca-tab [tabs]="state.entities" [activeTab]="state.selectedEntity"
19-
(tabSelected)="onTabSelected($event)"></hca-tab>
19+
(tabSelected)="onTabSelected($event, state.selectedSearchTermsBySearchKey)"></hca-tab>
2020
<hca-table-samples *ngIf="(state.selectedEntity).key == 'samples'"
2121
[selectedSearchTerms]="state.selectedSearchTerms"></hca-table-samples>
2222
<hca-table-files *ngIf="(state.selectedEntity).key == 'files'"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Facet } from "./facet/facet.model";
2+
import { SearchTerm } from "./search/search-term.model";
3+
import EntitySpec from "./shared/entity-spec";
4+
import { FileSummary } from "./file-summary/file-summary";
5+
6+
/**
7+
* Human Cell Atlas
8+
* https://www.humancellatlas.org/
9+
*
10+
* State backing files component.
11+
*/
12+
13+
export interface FilesComponentState {
14+
15+
entities: EntitySpec[];
16+
facets: Facet[];
17+
fileSummary: FileSummary;
18+
searchTerms: SearchTerm[];
19+
selectedEntity: EntitySpec;
20+
selectedProjectIds: string[];
21+
selectedSearchTerms: SearchTerm[];
22+
selectedSearchTermsBySearchKey: Map<string, Set<SearchTerm>>;
23+
}

0 commit comments

Comments
 (0)