Skip to content

Commit

Permalink
feat: add service support to analytics mechnism
Browse files Browse the repository at this point in the history
  • Loading branch information
kpanot committed Jul 2, 2024
1 parent 1c1a104 commit d570c4c
Show file tree
Hide file tree
Showing 58 changed files with 810 additions and 66 deletions.
2 changes: 1 addition & 1 deletion docs/analytics/ANALYTICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The practice of analytics is there for supporting decision-making by providing t
When you generate your component, you can decide to activate the otter analytics structure.

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

```typescript
Expand Down
120 changes: 72 additions & 48 deletions docs/analytics/TRACK_EVENTS.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/@o3r/analytics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@angular/router": "~18.0.0",
"@ngrx/store": "~18.0.0",
"@o3r/core": "workspace:^",
"@o3r/logger": "workspace:^",
"@o3r/schematics": "workspace:^",
"@schematics/angular": "~18.0.0",
"jasmine": "^5.0.0",
Expand Down Expand Up @@ -80,6 +81,7 @@
"@o3r/build-helpers": "workspace:^",
"@o3r/core": "workspace:^",
"@o3r/eslint-plugin": "workspace:^",
"@o3r/logger": "workspace:^",
"@o3r/test-helpers": "workspace:^",
"@schematics/angular": "~18.0.0",
"@stylistic/eslint-plugin-ts": "^2.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {<% if (activateDummy) { %>EventInfo, AnalyticsEvent, Attribute, ConstructorAnalyticsEvent, ConstructorAnalyticsEventParameters,<% } %> AnalyticsEvents} from '@o3r/analytics';
import type {<% if (activateDummy) { %>EventInfo, AnalyticsEvent, Attribute, ConstructorAnalyticsEvent, ConstructorAnalyticsEventParameters,<% } %> AnalyticsEvents} from '@o3r/analytics';

<% if (activateDummy) { %>/**
* Dummy event to show how we can use analytics event
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { EventInfo, AnalyticsEvent, Attribute, ConstructorAnalyticsEvent, ConstructorAnalyticsEventParameters, AnalyticsEvents} from '@o3r/analytics';

const dummyEvent = () => {

}

/**
* Dummy event to show how we can use analytics event
*/
export class DummyEvent implements AnalyticsEvent {
/** @inheritdoc */
public eventInfo: EventInfo = {
eventName: 'DummyEvent'
};
}

/**
* Interface to define the inputs that RuntimeDummyEvent needs to be created
*/
export interface RuntimeDummyEventConstructorAnalyticsEventParameters extends ConstructorAnalyticsEventParameters {
/**
* Example of runtime data
*/
runtimeData?: string;
}

/**
* Dummy event with runtime data to show how we can use analytics event
*/
export class RuntimeDummyEvent implements AnalyticsEvent {
/** @inheritdoc */
public eventInfo: EventInfo = {
eventName: 'RuntimeDummyEvent'
};

/** @inheritdoc */
public attributes: Attribute[]

constructor(parameters?: RuntimeDummyEventConstructorAnalyticsEventParameters) {
this.attributes = parameters?.runtimeData ? [{ key: 'runtimeData', value: parameters.runtimeData }] : [];
}
}

/**
* Interface for the analytics of <%= componentName %>
*/
export interface ComponentAnalytics extends AnalyticsEvents {
dummyEvent: ConstructorAnalyticsEvent<DummyEvent>;
runtimeDummyEvent: ConstructorAnalyticsEvent<RuntimeDummyEvent>;
}

/**
* Definition of the analytics of <%= componentName %>
*/
export const analyticsEvents: ComponentAnalytics = {
dummyEvent: DummyEvent,
runtimeDummyEvent: RuntimeDummyEvent
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,28 @@ export class EventTrackServiceFixture implements Readonly<Partial<EventTrackServ
/** @inheritdoc */
public addEvent: jasmine.Spy = jasmine.createSpy('addEvent');

/** @inheritdoc */
/**
* @inheritdoc
* @deprecated use {@link AnalyticsEventReporter} instead, will be removed in v12
*/
public addUiEvent: jasmine.Spy = jasmine.createSpy('addUiEvent');

/** @inheritdoc */
public toggleTracking: jasmine.Spy = jasmine.createSpy('toggleTracking');

/** @inheritdoc */
/**
* @inheritdoc
* @deprecated use {@link AnalyticsEventReporter} instead, will be removed in v12
*/
public toggleUiTracking: jasmine.Spy = jasmine.createSpy('toggleUiTracking');

/** @inheritdoc */
public togglePerfTracking: jasmine.Spy = jasmine.createSpy('togglePerfTracking');

/** @inheritdoc */
/**
* @inheritdoc
* @deprecated use {@link AnalyticsEventReporter} instead, will be removed in v12
*/
public addCustomEvent: jasmine.Spy = jasmine.createSpy('addCustomEvent');

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {BaseTrackEvents} from '../base-track-events';
/**
* Directive to capture the 'click' event on the reference element.
* The captured event will be exposed via EventTrackService
* @deprecated use {@link AnalyticTrackClick} instead, will be removed in v12
* @example
* ```html
* <my-component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {BaseTrackEvents} from './base-track-events';
/**
* Directive to capture the events given as input, on the reference element.
* The captured event will be exposed via EventTrackService
* @deprecated use {@link AnalyticTrackEvents} instead, will be removed in v12
* @example
* ```html
* <my-component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {BaseTrackEvents} from '../base-track-events';
/**
* Directive to capture the 'focus' event on the reference element.
* The captured event will be exposed via EventTrackService
* @deprecated use {@link AnalyticTrackFocus} instead, will be removed in v12
* @example
* ```html
* <my-component
Expand Down
4 changes: 4 additions & 0 deletions packages/@o3r/analytics/src/performance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './contracts/index';
export * from './directives';
export * from './services/index';
export * from './stores/index';
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@ import { InjectionToken } from '@angular/core';

/** Controls the activation of mesurements and ui events tracking */
export interface TrackActive {
/** Boolean to activate/deactivate the ui event tracking */
/**
* Boolean to activate/deactivate the ui event tracking
* @deprecated use {@link AnalyticsEventReporter.isTrackingActive} instead, will be removed in v12. Need to be turned to `false` when using {@link AnalyticsEventReporter}.
**/
uiTracking: boolean;
/** Boolean to activate/deactivate performace measurements */
/** Boolean to activate/deactivate performance measurements */
perfTracking: boolean;
}

/** Track events service configuration object */
export interface EventTrackConfiguration {
/** Defines how many values will be kept in performance metrics stream */
perfBufferSize: number;
/** Defines how many values will be kept in ui events stream */
/**
* Defines how many values will be kept in ui events stream
* @deprecated use {@link AnalyticsEventReporter} instead, will be removed in v12
**/
uiEventsBufferSize: number;
/** If true, it will use the browser API to get the value of the FP for the first time; */
useBrowserApiForFirstFP: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,25 @@ export class EventTrackService {

private firstPaint?: Promise<EventTiming>;

/** UI captured events as stream */
/**
* UI captured events as stream
* @deprecated use {@link AnalyticsEventReporter} instead, will be removed in v12
*/
public uiEventTrack$: Observable<UiEventPayload>;

/** Custom captured events as stream */
/**
* Custom captured events as stream
* @deprecated use {@link AnalyticsEventReporter} instead, will be removed in v12
*/
public customEventTrack$: Observable<CustomEventPayload>;

/** Performance captured events as stream */
public perfEventTrack$: Observable<PerfEventPayload>;

/** Stream of booleans for the ui tracking mode active/inactive */
/**
* Stream of booleans for the ui tracking mode active/inactive
* @deprecated use {@link AnalyticsEventReporter} instead, will be removed in v12
*/
public uiTrackingActive$: Observable<boolean>;

/** Stream of booleans for the performance tracking mode active/inactive */
Expand Down Expand Up @@ -329,6 +338,7 @@ export class EventTrackService {
/**
* Add an event to the stream of captured UI events
* @param uiEvent emitted event object
* @deprecated use {@link AnalyticsEventReporter.reportEvent} instead, will be removed in v12
*/
public addUiEvent(uiEvent: UiEventPayload) {
this.uiEventTrack.next(uiEvent);
Expand All @@ -345,6 +355,7 @@ export class EventTrackService {
/**
* Activate/deactivate the tracking mode for UI events
* @param activate activation/deactivation boolean
* @deprecated use {@link AnalyticsEventReporter.isTrackingActive} instead, will be removed in v12
*/
public toggleUiTracking(activate: boolean) {
this.uiTrackingActivated.next(activate);
Expand Down
File renamed without changes.
7 changes: 2 additions & 5 deletions packages/@o3r/analytics/src/public_api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
export * from './contracts/index';
export * from './directives/index';
export * from './services/index';
export * from './stores/index';

export * from './performance/index';
export * from './tracker/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Directive, input } from '@angular/core';
import { AnalyticTrackGeneric } from './generic-event.directive';

@Directive({
selector: '[trackClick]',
standalone: true
})
/** Directive to listen and emit analytics in case of Dom Click event */
export class AnalyticTrackClick extends AnalyticTrackGeneric {
/** @inheritdoc */
public trackEvent = input(undefined, { alias: 'trackClick' });
/** @inheritdoc */
public trackCategory = input('', { alias: 'trackClickCategory' });
/** @inheritdoc */
public trackAction = input(undefined, { alias: 'trackClickAction' });
/** @inheritdoc */
public trackLabel = input(undefined, { alias: 'trackClickLabel' });
/** @inheritdoc */
public trackValue = input(undefined, { alias: 'trackClickValue' });
/** @inheritdoc */
public readonly eventName = 'click';
}
82 changes: 82 additions & 0 deletions packages/@o3r/analytics/src/tracker/directives/events.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Directive, effect, ElementRef, inject, input,type OnInit, Renderer2 } from '@angular/core';
import { AnalyticsEventReporter } from '../services/tracker/analytics-reporter.service';
import type { AnalyticsWellKnownDomActionType } from '../events';

type TrackEventName = keyof GlobalEventHandlersEventMap;

@Directive({
selector: '[trackEvents]',
standalone: true
})
export class AnalyticTrackEvent implements OnInit {
/** List the Dom Element events to listen and for which emitting analytics event. */
public trackEvents = input.required<TrackEventName[]>();
/** Category of the events */
public trackCategory = input<string>('');
/**
* Name of the action as defined in analytics service.
* The name of the Dom Event will be used if not specified.
*/
public trackAction = input<AnalyticsWellKnownDomActionType | undefined>();
/** Label of the events */
public trackLabel = input<string | undefined>();
/** Value of the events */
public trackValue = input<any>();

protected readonly el = inject(ElementRef);
protected readonly trackEventsService = inject(AnalyticsEventReporter);
protected readonly renderer = inject(Renderer2);
protected readonly isTrackingActive;
protected listeningEvents: {[x in TrackEventName]?: () => void} = {};

constructor() {
this.isTrackingActive = this.trackEventsService.isTrackingActive;
}

/**
* Create the listener for the given event
* @param eventName name of the event to listen
*/
protected nativeListen(eventName: TrackEventName) {
// Renderer is used because it is manipulating the DOM and when the element is destroyed the event listener is destroyed too.
// Usage of an observable from event was not possible because the ngOnDestroy with the unsubscribe was called before the ui event was handled
return this.renderer.listen(this.el.nativeElement, eventName, (event) => {
const action = this.trackAction() || eventName as AnalyticsWellKnownDomActionType;
this.trackEventsService.reportEvent({
type: 'event',
action,
category: this.trackCategory(),
label: this.trackLabel(),
event,
attributes: this.trackValue() || this.el.nativeElement?.value
});
});
}

/** Remove the created events listeners */
protected unlisten() {
Object.values(this.listeningEvents).forEach((fn) => fn);
this.listeningEvents = {};
}

public ngOnInit(): void {
effect(() => {
const analyticEvent = this.trackEvents();
if (this.isTrackingActive()) {
this.listeningEvents = {
...this.listeningEvents,
...Object.fromEntries(analyticEvent
.filter((e) => !this.listeningEvents[e])
.map((e) => [e, this.nativeListen(e)])
)
};
}
});

effect(() => {
if (!this.isTrackingActive()) {
this.unlisten();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Directive, input } from '@angular/core';
import { AnalyticTrackGeneric } from './generic-event.directive';

@Directive({
selector: '[trackFocus]',
standalone: true
})
/** Directive to listen and emit analytics in case of Dom Focus event */
export class AnalyticTrackFocus extends AnalyticTrackGeneric {
/** @inheritdoc */
public trackEvent = input(undefined, { alias: 'trackFocus' });
/** @inheritdoc */
public trackCategory = input('', { alias: 'trackFocusCategory' });
/** @inheritdoc */
public trackAction = input(undefined, { alias: 'trackFocusAction' });
/** @inheritdoc */
public trackLabel = input(undefined, { alias: 'trackFocusLabel' });
/** @inheritdoc */
public trackValue = input(undefined, { alias: 'trackFocusValue' });
/** @inheritdoc */
public readonly eventName = 'focus';
}
Loading

0 comments on commit d570c4c

Please sign in to comment.