Skip to content

Commit 4ede4dd

Browse files
committed
feat: add service support to analytics mechnism
1 parent 3ffc0ba commit 4ede4dd

File tree

66 files changed

+1983
-488
lines changed

Some content is hidden

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

66 files changed

+1983
-488
lines changed

apps/showcase/src/style/dark-theme/dark-theme.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,4 @@
9696
--spacing-30: 30px;
9797
--spacing-40: 40px;
9898
/* --- END THEME Auto-generated --- */
99-
}
99+
}

apps/showcase/src/style/horizon-theme/horizon-theme.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,4 @@
8787
--spacing-30: 30px;
8888
--spacing-40: 40px;
8989
/* --- END THEME Auto-generated --- */
90-
}
90+
}

docs/analytics/ANALYTICS.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
# Analytics
2-
Here, you will see how to build step by step your own component UI events to be tracked by the [Analytics Track Event Service](./TRACK_EVENTS.md).
2+
3+
> [!IMPORTANT]
4+
> This documentation is referring to an outdated way to emit analytics events, please referrer to [Track Events documentation](./TRACK_EVENTS.md) to latest version.
5+
> The mechanism described in this document will be removed in Otter v12.
6+
7+
Here, you will see how to build step by step your own component UI events to be tracked by the [Analytics Track Event Service *(deprecated)*](./TRACK_EVENTS(deprecated).md).
38

49
## Context
10+
511
The practice of analytics is there for supporting decision-making by providing the relevant facts that will allow you to make better choices.
612

713
## How to use
14+
815
When you generate your component, you can decide to activate the otter analytics structure.
916

1017
### A new file analytic.ts
11-
The otter component generator will create one file suffixed by `analytics.ts`.
18+
19+
The otter component generator will create one file suffixed by `analytics.ts`.
1220
Inside you will find an interface to define all the events that your component can trigger and a const to inject inside your component.
1321

1422
```typescript
@@ -52,6 +60,7 @@ export const analyticsEvents: MyComponentAnalytics = {
5260
```
5361

5462
### Component file
63+
5564
Your component needs to implement _Trackable_ interface.
5665

5766
```typescript
@@ -70,6 +79,8 @@ class MyComponent implements Trackable<MyComponentAnalytics>, ... {
7079
}
7180
```
7281

73-
## TrackEvents
82+
## References
7483

75-
Check [TRACK_EVENTS.md](./TRACK_EVENTS.md)
84+
- Track Analytics Events following [Track Events documentation](./TRACK_EVENTS.md)
85+
- Track Performance Metrics via [Performance measurement documentation](./PERFORMANCE.md)
86+
- *(:warning: deprecated)* Track Analytics Events following [Track Events module documentation](./TRACK_EVENTS(deprecated).md)

docs/analytics/PERFORMANCE.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# Performance metrics
2+
3+
There are several aspects of a web application that can impact its performance. Network conditions, CPU processing, server-side tasks are a few of them.
4+
Checking how long it took to load the page is not enough to measure the application performances.
5+
Quickly loading something that is not meaningful nor interactive means nothing to the user. That's why one must improve the load
6+
time AND the perceived performance (aka how fast the user perceives the application).
7+
Some of those metrics (load time related and perception metrics) are described below.
8+
9+
## Metrics
10+
11+
### First load
12+
13+
Mark the first load metrics using the [Performance API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming).
14+
This has to be called only once in a single page application, as it is only meaningful for the initial page load. [FirstLoadDataPayload](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/analytics/src/contracts/events-contracts.ts)
15+
interface is the model object for this mark.
16+
17+
### First paint ([FP](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint))
18+
19+
This is one of the first metrics for perceived performance. Basically, it measures the time the app takes to answer a
20+
user's first question: Is something happening? Is the navigation successful ? Has the server responded?
21+
The First Paint (FP) measures the time it takes from the start of the navigation to, for example, display the loading indication.
22+
23+
### First Meaningful Paint ([FMP](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint))
24+
25+
Also for perceived performance, FMP measures the time the app takes to render enough content for users to engage. A simple strategy for this metric is to mark what's called hero elements (most important
26+
elements in the page) and register the delay to display them.
27+
28+
### Time to Interactive ([TTI](https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive))
29+
30+
TTI marks the time when the user can effectively interact with the app. This is closely related to the fact that, in some implementations, the app may have rendered meaningful information
31+
(measured by FMP) but, in the background, it's still doing some kind of computation that blocks any possible interaction with the page.
32+
33+
The time to interactive is quite tricky as it not only depends on the relevant data readiness, but also on
34+
component internal display mechanics.
35+
If you know exactly where javascript will trigger a layout change (e.g. by passing a boolean variable to true), it's possible to measure the upper bound for the rendering.
36+
37+
In addition, during a component development, you can't possibly know beforehand if the component will be relevant for a TTI or not, since it depends on the page itself.
38+
For example, the display of a cart component may be relevant for TTI in a given page and not relevant at all in others.
39+
Hence, you cannot really define your TTI logic at component level.
40+
41+
Given the above facts, we advise to split the TTI metric in two:
42+
43+
* __dataReady__: This probe marks the time when all the data, needed to the page be interactive, is available
44+
* __TTI per component__: data ready for each component; we advise to implement it later, since it may impact the complexity of the code
45+
46+
For the time being, we will consider only the implementation of __data ready__.
47+
48+
### Network and server-side metrics
49+
50+
As the browser can't understand when a route event happens in an SPA, the NavigationTimingAPI can't be directly used apart from the first page load at most.
51+
Subsequent routing changes won't profit from the API connection timings.
52+
53+
In regard of the __server fetches__ (filter out from the resource timing API), the [PerformanceMetricPlugin](https://github.com/AmadeusITGroup/otter/blob/main/packages/@ama-sdk/core/src/plugins/perf-metric/perf-metric.fetch.ts)
54+
has been put in place to get the metrics associated to server calls.
55+
Check [ServerCallMetric](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/analytics/src/contracts/events-contracts.ts)
56+
model to see which information is saved for each call.
57+
58+
## How to mark performance metrics?
59+
60+
The __EventTrackService__ plugs itself to the [NavigationEnd](https://angular.io/api/router/NavigationEnd) router, to handle the performance metrics and expose the performance object as a stream (observable).
61+
The performance metric object structure is defined by __PerfEventPayload__ interface which can be found [here](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/analytics/src/contracts/events-contracts.ts).
62+
The service provides a way to activate/deactivate performance measurements. By default, it's __activated__ and we expose a public method called __togglePerfTracking__ to activate/deactivate it.
63+
For instance if you want to deactivate it, call this in your app:
64+
65+
```typescript
66+
import {EventTrackService} from '@o3r/analytics';
67+
...
68+
constructor(trackService: EventTrackService) {
69+
trackService.togglePerfTracking(false);
70+
}
71+
```
72+
73+
### Tracking configuration
74+
75+
You can override the default configuration via a configuration token ([EVENT_TRACK_SERVICE_CONFIGURATION](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/analytics/src/services/event-track/event-track.configuration.ts)).
76+
Example of configuration override:
77+
78+
```typescript
79+
// in app module
80+
...
81+
providers: [
82+
...
83+
{provide: EVENT_TRACK_SERVICE_CONFIGURATION, useValue: {useBrowserApiForFirstFP: true}}
84+
]
85+
```
86+
87+
More details about the configuration object and [defaultEventTrackConfiguration](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/analytics/src/services/event-track/event-track.configuration.ts) can be found in the [event tracking documentation](https://github.com/AmadeusITGroup/otter/blob/main/packages/@o3r/analytics/src/services/event-track/event-track.configuration.ts)
88+
89+
#### First load measurement
90+
91+
This mark is populated by default by the __EventTrackService__ when the [NavigationEnd](https://angular.io/api/router/NavigationEnd) event of the router emits for the first time.
92+
93+
#### First paint (FP)
94+
95+
You can mark the time the loading is rendered.
96+
97+
* If the app has a loading indicator at [NavigationStart](https://angular.io/api/router/NavigationStart), this is when we want to mark the first paint.
98+
99+
```typescript
100+
// app component
101+
...
102+
constructor(private router: Router, public trackEventsService: EventTrackService) {}
103+
ngOnInit() {
104+
this.subscriptions.push(this.router.events.subscribe((event) => this.setLoadingIndicator(event)));
105+
...
106+
}
107+
setLoadingIndicator(event: Event) {
108+
if (event instanceof NavigationStart) {
109+
this.loading = true;
110+
this.trackEventsService.markFP(); // ----> mark the first paint here
111+
}
112+
}
113+
```
114+
115+
* If __index.html__ contains a loading indicator, it will be rendered even before loading angular;
116+
In this case FP will be marked by the browser api. You can activate this behaviour in the tracking service and override the '_useBrowserApiForFirstFP_' config property to _true_;
117+
If the browser does not have [performance entry 'paint' api](https://developer.mozilla.org/en-US/docs/Web/API/Performance/getEntriesByType), nothing will be marked.
118+
119+
```typescript
120+
// in app module
121+
...
122+
providers: [
123+
...
124+
{provide: EVENT_TRACK_SERVICE_CONFIGURATION, useValue: {useBrowserApiForFirstFP: true}}
125+
]
126+
```
127+
128+
* __markFP__ method from tracking service should be called when the loading indicator is triggered
129+
130+
#### First Meaningful Paint (FMP)
131+
132+
You can mark FMP is in the _ngAfterViewInit_ of each page
133+
134+
```typescript
135+
// Search component
136+
137+
constructor(... , private trackEventsService: EventTrackService) {...}
138+
139+
ngAfterViewInit() {
140+
this.trackEventsService.markFMP();
141+
}
142+
```
143+
144+
#### Data Ready
145+
146+
This will depend on your application.
147+
For example, on the availability page, mark _data ready_ when the calendar and offers data are available;
148+
149+
```typescript
150+
// upsell page component
151+
...
152+
export class UpsellComponent implements OnInit, OnDestroy, Configurable<UpsellConfig> {
153+
...
154+
constructor(public trackEventsService: EventTrackService, private store: Store<AirOffersStore & AirSearchCriteriaStore & CartStore & AirCalendarStore>) {
155+
...
156+
}
157+
158+
ngOnInit() {
159+
const airCalendarReady$ = this.store.pipe(
160+
select(selectAirCalendarState),
161+
filter((state) => state.isPending === false && state.isFailure === false)
162+
);
163+
const airOffersReady$ = this.store.pipe(
164+
select(selectAirOffersIds),
165+
filter((ids) => !!ids.length)
166+
);
167+
this.subscriptions.push(
168+
combineLatest(airCalendarReady$, airOffersReady$)
169+
.pipe(take(1))
170+
.subscribe(([_airCalendar, _airOffersIds]) => {
171+
this.trackEventsService.markDataReady(); /// ----> mark data ready when both calendar and offres data are in the store
172+
}));
173+
}
174+
...
175+
}
176+
```
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Track UI Events
2+
3+
The main purpose of this mechanism is to ease event tracking at component level.
4+
You can capture your events via the tracking event directives (exposed in the [TrackEventsModule](#trackeventsmodule))
5+
and the [EventTrackService](#eventtrackservice).
6+
7+
You can access all these events via the [EventTrackService](#eventtrackservice).
8+
9+
## EventTrackService
10+
11+
This service is used to store the event objects and to expose them as a stream (observable) to your application.
12+
It controls the analytics activation and deactivation as a whole or per feature (ui, performance etc.).
13+
14+
You can directly access the service `EventTrackService` inside your component to capture new events.
15+
16+
```typescript
17+
import {EventTrackService} from '@o3r/analytics';
18+
import {analyticsEvents, MyComponentAnalytics} from './my-component.analytics';
19+
20+
class MyComponent extends Trackable<MyComponentAnalytics>, ... {
21+
...
22+
23+
/**
24+
* @inheritDoc
25+
*/
26+
public readonly analyticsEvents: MyComponentAnalytics = analyticsEvents;
27+
28+
constructor(..., private eventTrackService: EventTrackService) {
29+
...
30+
}
31+
32+
...
33+
34+
somethingHappened() {
35+
this.eventTrackService.addUiEvent(new analyticsEvents.dummyEvent())
36+
}
37+
}
38+
```
39+
40+
## TrackEventsModule
41+
42+
The `TrackEventsModule` contains directives to help you track standard event such as the `TrackClickDirective` or
43+
`TrackFocusDirective`.
44+
You can track more standard ui event with the `TrackEventsDirective` and even create your own component events
45+
(see [Analytics Events](./ANALYTICS.md)).
46+
Note that all these events will be stored as UI Events in the [EventTrackService](#eventtrackservice).
47+
48+
```html
49+
<button
50+
(click)="doSomething()"
51+
trackClick
52+
[trackEventContext]="analyticsEvents.dummyEvent()"></button>
53+
<button
54+
(click)="doSomethingElse()"
55+
trackClick
56+
[trackEventContext]="analyticsEvents.runtimeDummyEvent('You could send runtime data here')"></button>
57+
```
58+
59+
### TrackEvents directive
60+
61+
The directive will listen to the events on the element on which was applied and will expose the event captured using the track service.
62+
63+
| Input Name | Description | Possible Values |
64+
| ----------------- | ------------------------------------------------------ | ------------------------------- |
65+
| trackEvents | List of events which have to be tracked | ['mouseover', 'mouseenter'] |
66+
| trackEventContext | Custom object to be exposed when the event is captured | {context: 'continueBtnClicked'} |
67+
68+
A specific directive for the click event was created, as it is the most used tracked event.
69+
70+
### Directive usage
71+
72+
```html
73+
<button type="button" *ngFor="let calendarDate of calendarDates"
74+
[attr.selected]="isSelected"
75+
(click)="onDateSelection()"
76+
[trackEventContext]="{name: 'selectDate', dateSelected: calendarDate}"
77+
[trackEvents]="['click']" <!-- or simply 'trackClick' -->
78+
>
79+
```
80+
81+
If the object passed in `trackEventContext` has to be updated in the model file (ts):
82+
83+
* it can be done calling a function (the drawback here is that a function is called too many times if it is used in the templates directly);
84+
* it can be done on the event handler function if exists - you might need to call the detectChanges if a navigation occurs or the component is repainted, not recommended;
85+
* it can be done via a pipe which will update the object - recommended solution as a pipe is pure by default meaning that if the inputs have not changed the transform will not be called;
86+
87+
```html
88+
<form [formGroup]="searchForm">
89+
...
90+
<button
91+
[trackEvents]="['click', 'mouseover']"
92+
[trackEventContext]="{value: eventModel | eventContext : {departure: searchForm.controls['departureLocation'], arrival: searchForm.controls['arrivalLocation']}}"
93+
[disabled]="!searchForm.valid"
94+
[attr.id]="id + '-searchButton'">Search</button>
95+
...
96+
</form>
97+
```
98+
99+
in component.ts file
100+
101+
```typescript
102+
eventModel = {name: 'searchBtnMouseEvent'};
103+
```
104+
105+
in eventContext pipe.ts file
106+
107+
```typescript
108+
transform(value: any, itinerary: any): any {
109+
return {...value, itinerary};
110+
}
111+
```
112+
113+
### Application level
114+
115+
At application level a subscription can be done to the observable emitted by the track events service.
116+
You can enhance your analytics data and merge/concatenate/modify the event from the `TrackEventsService` with your own
117+
application store.
118+

0 commit comments

Comments
 (0)