Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat(analytics): système de régulation des actions [DS-3799] #937

Merged
merged 3 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/analytics/doc/analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Analytics

> **Note**
> Ajout de la propriété `isActionEnabled` dans la configuration et sur l'objet `window.dsfr.analytics` permettant d'activer l'envoi de l'ensemble des données d'actions des composants. Pour activer l'envoi d'action sur des éléments spécifiques au cas par cas, il est possible d'ajouter l'attribut `data-fr-analytics-action` sur l'élément.
> Ajout de la propriété `isActionEnabled` dans la configuration et sur l'objet `window.dsfr.analytics` permettant d'activer l'envoi de l'ensemble des données d'actions des composants. Pour activer l'envoi d'action sur des éléments spécifiques au cas par cas, il est possible d'ajouter l'attribut `data-fr-analytics-action` sur l'élément. À l'inverse, lorsque l'envoi d'action est activé au global, l'attribut avec la valeur `data-fr-analytics-action="false"` permet de désaciver l'envoi sur un élément.

> **Important**
> La propriété de configuration `enableRating` a été retirée, celle-ci entraînant des envois de données trop importants. En remplacement, un attribut `data-fr-analytics-rating` peut être ajouté sur un élément dont on veut mesurer spécifiquement le taux de click.
Expand Down
1 change: 1 addition & 0 deletions src/analytics/doc/analytics/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ exemple d’actionName : `(click)_titre_niveau_2_›_titre_niveau_3_›_label_de

Par défaut, l'envoi des actions est désactivé. Le paramètre de configuration `isActionEnabled` permet de l'activer. (voir [isActionEnabled dans Analytics](collector/analytics.md#isActionEnabled)).
Il est possible de d'activer l'envoi sporadiquement sur un élément particulier en utilisant l'attribut `data-fr-analytics-action`, qui permet également de donner une valeur spécifique au title de l'[ActionName](#ActionName).
À l'inverse, il est possible de désactiver l'envoi d'actions sur un élément particulier en utilisant l'attribut `data-fr-analytics-action="false"` lorsque l'envoi d'actions est activé au global.

#### Taux d'interaction

Expand Down
3 changes: 2 additions & 1 deletion src/analytics/doc/analytics/collector/analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,12 @@ _Boolean_

`window.dsfr.analytics.isActionEnabled`

Permet d’activer / désactiver la mesure d'audience des actions.
Permet d’activer / désactiver la mesure d'audience des actions au niveau global.

* Par défaut, la mesure d'audience des actions est désactivée.

Défini dans la configuration (voir propriété `isActionEnabled` de la [configuration](../installation/configuration.md))
Voir [Activer les actions](../actions.md#Activer les actions) pour plus d'informations sur l'activation ou la désactivation des actions au cas par cas.

* * *

Expand Down
14 changes: 14 additions & 0 deletions src/analytics/example/action/index.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div class="<%= prefix %>-container" >
<div>
<h4>action activée</h4>
<div class="<%= prefix %>-mb-6v">
<button class="<%= prefix %>-btn" data-<%= prefix %>-analytics-action="libellé envoyé à l'analytics" id="action-true" >libellé du bouton</button>
</div>
</div>
<div>
<h4>action prévenue</h4>
<div class="<%= prefix %>-mb-6v">
<button class="<%= prefix %>-btn" data-<%= prefix %>-analytics-action="false" id="action-false" >libellé du bouton</button>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion src/analytics/example/config.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
analytics: {
domain: 'gva.et-gv.fr',
// collection: 'manual', // method of collection [manual, load, full, hash]
// isActionEnabled: true, // ensable action tracking
isActionEnabled: true, // enable action tracking
cmp: {
id: 'tarteaucitron'
},
Expand Down
2 changes: 1 addition & 1 deletion src/analytics/i18n/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ description:
doc:
subdir:
title: Pages
action: Activation des actions
attribute: Attributs
component: Composants
agnostic: sans framework
Expand Down Expand Up @@ -76,4 +77,3 @@ subdir:
translate: Sélecteur de langue (translate)
upload: Ajout de fichier (upload)
vue: Vue (vue)

16 changes: 13 additions & 3 deletions src/analytics/script/analytics/action/action-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import api from '../../../api';
import actions from './actions';
import { Hierarchy } from '../utils/hierarchy/hierarchy';
import queue from '../engine/queue';
import { ActionRegulation } from './action-regulation';

class ActionElement {
constructor (node, type, id, category = '', title = null, parameters = {}, isRating = false, isForced = false) {
constructor (node, type, id, category = '', title = null, parameters = {}, isRating = false, regulation = ActionRegulation.NONE) {
this._node = node;
this._type = type;
this._id = id || this._node.id;
Expand All @@ -13,7 +14,7 @@ class ActionElement {
this._category = category;
this._parameters = parameters;
this._isRating = isRating;
this._isForced = isForced;
this._regulation = regulation;
this._hasBegun = false;

// this._init();
Expand All @@ -34,7 +35,7 @@ class ActionElement {
if (this._type.isSingular) this._action.singularize();
Object.keys(this._parameters).forEach(key => this._action.addParameter(key, this._parameters[key]));
this._action.isMuted = this._isMuted;
this._action.isForced = this._isForced;
this._action.regulation = this._regulation;

this._action.labels[0] = this._type.id;
this._action.labels[1] = this._hierarchy.globalComponent;
Expand All @@ -57,6 +58,15 @@ class ActionElement {
if (this._action) this._action.isMuted = value;
}

get regulation () {
return this._regulation;
}

set regulation (value) {
this._regulation = value;
if (this._action) this._action.regulation = value;
}

get action () {
return this._action;
}
Expand Down
5 changes: 5 additions & 0 deletions src/analytics/script/analytics/action/action-regulation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const ActionRegulation = {
ENFORCE: 'enforce',
PREVENT: 'prevent',
NONE: 'none'
};
11 changes: 6 additions & 5 deletions src/analytics/script/analytics/action/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import normalize from '../utils/normalize';
import { ActionMode } from './action-mode';
import { validateString } from '../utils/type-validator';
import { ActionStatus } from './action-status';
import { ActionRegulation } from './action-regulation';

const getParametersLayer = (data) => {
return Object.entries(data).map(([key, value]) => ['actionpname', normalize(key), 'actionpvalue', normalize(value)]).flat();
Expand All @@ -11,7 +12,7 @@ const getParametersLayer = (data) => {
class Action {
constructor (name) {
this._isMuted = false;
this._isForced = false;
this._regulation = ActionRegulation.NONE;
this._name = name;
this._status = ActionStatus.UNSTARTED;
this._labels = [];
Expand All @@ -27,12 +28,12 @@ class Action {
this._isMuted = value;
}

get isForced () {
return this._isForced;
get regulation () {
return this._regulation;
}

set isForced (value) {
this._isForced = value;
set regulation (value) {
if (Object.values(ActionRegulation).includes(value)) this._regulation = value;
}

get isSingular () {
Expand Down
29 changes: 19 additions & 10 deletions src/analytics/script/analytics/engine/queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PushType from '../facade/push-type.js';
import push from '../facade/push.js';
import renderer from './renderer';
import api from '../../../api';
import { ActionRegulation } from '../action/action-regulation';

const SLICE = 80;

Expand Down Expand Up @@ -42,23 +43,31 @@ class Queue {
this._request();
}

appendStartingAction (action, data) {
if (!this._collector.isActionEnabled && !action.isForced) return;
if (!action || this._startingActions.some(queued => queued.test(action))) {
api.inspector.log('appendStartingAction exists or null', action);
return;
regulate (action, queue) {
if (!action) return false;
if (queue.some(queued => queued.test(action))) {
api.inspector.log('action exists in queue', action);
return false;
}
switch (action.regulation) {
case ActionRegulation.PREVENT:
return false;
case ActionRegulation.ENFORCE:
return true;
default:
return this._collector.isActionEnabled;
}
}

appendStartingAction (action, data) {
if (!this.regulate(action, this._startingActions)) return;
const queued = new QueuedAction(action, data);
this._startingActions.push(queued);
this._request();
}

appendEndingAction (action, data) {
if (!this._collector.isActionEnabled && !action.isForced) return;
if (!action || this._endingActions.some(queued => queued.test(action))) {
api.inspector.log('appendEndingAction exists or null', action);
return;
}
if (!this.regulate(action, this._endingActions)) return;
const queued = new QueuedAction(action, data);
this._endingActions.push(queued);
this._request();
Expand Down
37 changes: 34 additions & 3 deletions src/analytics/script/integration/core/actionee.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import api from '../../../api.js';
import { Type } from '../../analytics/action/type';
import { ActionElement } from '../../analytics/action/action-element';
import { ActioneeEmission } from './actionee-emission';
import { ActionRegulation } from '../../analytics/action/action-regulation';
import normalize from '../../analytics/utils/normalize';

const ActionAttributes = {
RATING: api.internals.ns.attr('analytics-rating'),
ACTION: api.internals.ns.attr('analytics-action')
};

class Actionee extends api.core.Instance {
constructor (priority = -1, category = '', title = null, isForced = false) {
constructor (priority = -1, category = '', title = null, regulation = ActionRegulation.NONE) {
super();
this._type = null;
this._priority = priority;
Expand All @@ -18,7 +20,7 @@ class Actionee extends api.core.Instance {
this._parameters = {};
this._data = {};
this._isMuted = false;
this._isForced = isForced;
this._regulation = regulation;
}

static get instanceClassName () {
Expand Down Expand Up @@ -66,14 +68,43 @@ class Actionee extends api.core.Instance {
return;
}

this._actionElement = new ActionElement(this.node, this._type, this.id, this._category, this.getAttribute(ActionAttributes.ACTION) || this._title, this._parameters, this.hasAttribute(ActionAttributes.RATING), this.hasAttribute(ActionAttributes.ACTION) || this._isForced);
const regulation = this.getRegulation();
this._regulation = regulation !== ActionRegulation.NONE ? regulation : this._regulation;
const actionAttribute = this.getAttribute(ActionAttributes.ACTION);
const title = typeof actionAttribute === 'string' && actionAttribute.toLowerCase() !== 'false' && actionAttribute.toLowerCase() !== 'true' ? normalize(actionAttribute) : this._title;
this._isRating = this.hasAttribute(ActionAttributes.RATING);

this._actionElement = new ActionElement(this.node, this._type, this.id, this._category, title, this._parameters, this._isRating, this._regulation);
if (this._isMuted) this._actionElement.isMuted = true;

this.addDescent(ActioneeEmission.REWIND, this.rewind.bind(this));

this._sort(element);
}

getRegulation () {
const actionAttribute = this.getAttribute(ActionAttributes.ACTION);
switch (true) {
case typeof actionAttribute === 'string' && actionAttribute.toLowerCase() === 'false':
return ActionRegulation.PREVENT;
case actionAttribute !== null:
return ActionRegulation.ENFORCE;
default:
return ActionRegulation.NONE;
}
}

mutate (attributeNames) {
if (attributeNames.includes(ActionAttributes.ACTION)) {
const regulation = this.getRegulation();
if (this._regulation !== regulation) {
this._regulation = regulation;
if (this._actionElement) this._actionElement.regulation = regulation;
}
}
super.mutate(attributeNames);
}

_sort (element) {
const actionees = element.instances.filter(instance => instance.isActionee).sort((a, b) => b.priority - a.priority);
if (actionees.length <= 1) return;
Expand Down