Refonte de l'espace client

Bouygues Telecom

sbachelier

carlogren

Présentation

  • Contexte du projet
  • Rework​

Contexte du projet

La motivation

  • Meilleure expérience client
    • Vue 360 : Multi-device / Omnicanal
    • Simplicité
    • Performance
  • Transformation profonde du SI
    • Simplification / Modernisation : WOA
    • Réduction des coûts 

L'existant

  • Espace Client COnvergeant (ECCO pour les intimes)

    • Architecture Client / Serveurs "classique" SOA

    • PHP / Zend

    • Web Services SOAP/XML

Choix de Backbone

  • Flexibilité / Maîtrise du code
  • Ecosystème important
  • Annonce d'Angular 2

Organisation

Méthode Agile

SCRUM - Sprint 2 semaines

  • 1 Team Leader
  • 1 Arch. Technique
  • 1 Développeur
  • 1 Team Leader
  • 1 PO (MOA)
  • 1 Arch. Technique
  • 1 Resp. Etudes
  • 2 Développeurs
  • 1 Intégrateur
  • 1 Team Leader
  • 1 PO (MOA)
  • 1 Arch. Technique
  • 1 Resp. Etude
  • 3 Développeurs
  • 1 Intégrateur

Janvier (POC)

Avril

Juin (Shift tech.)

Dream Team

  • 1 Team Leader
  • 1 PO (MOA)
  • 1 Arch. Technique
  • 1 Resp. Etude
  • 3 Développeurs
  • 1 Intégrateur
  • 1 Analyste / Tests

Octobre (MEP)

Rework

  • Janvier 2015 : Lancement
  • Juillet 2015 : Shift
  • Octobre 2015 : MEP 1.0

Lancement de la refonte

  • Création d'un POC
  • Une première version avec Backbone et Thorax
    • Authentification via OAuth (JWT)
    • Dashboard
    • Synthèse mono ligne
    • Navigation
    • Responsive (Desktop, Tablette, Mobile)

Shift technique

Juillet 2015

Socle technique

  • Backbone.Marionette

  • Require.js

  • Workflow avancé avec Grunt

  • Tests unitaires avec Karma, Mocha, Chai, Sinon

 

  Correspond à l'arrivée de nouvelles ressources

Contraintes

  • Plus que 3 mois pour la refonte avant MEP

  • Démo fonctionnelle en fin de sprint

  • Construction des ressources en // de l'application

  • Cohabitation avec certaines parties de l'espace client

  • Congés d'été

Besoins fonctionnels

  • Intégration de marketing ciblé (Revive Ad Server)

  • Sécurité (HTTPS, SSO)

  • Intégration et interconnexion avec SalesForce

  • Exploitation de la nouvelle API REST

  • Cohabitation du nouvel espace client avec l'existant

  • Monitoring (Kibana)

  • Analytics (XiTi)

Un peu de technique :)

Configuration

  • Cinq environnements principaux

    • Dev, Recette, Intégration, Pre-prod, Prod

  • Gestion des URLs différentes suivant les environnements

  • i18n : Une seule langue (FR)

    • Volonté de séparer tous les champs textes

    • Gestion effectuée par la MOA

 

  • Utilisation du format YAML

  • Automatisation avec des tâches Grunt

Boot Loader

  • S'exécute avant le lancement de l'application

  • Un certain nombre de vérifications sont effectuées

    • Authentification

    • Test du profil client (redirection)

    • Participation à la beta

    • Initialisation de certaines données

Authentification

  • Utilisation d'un SSO (Single Sign ON)

  • OAuth 2

  • JWT (JSON Web Token)

  • HTTPS !!!

 

  • Token généré et signé par le serveur

  • Décrypté côté client :

    • Librairie jsrasign

    • Contient un certain nombre de claims, dont la date d'expiration

    • Extraction des informations utilisateur

Authentification

  • Récupération du token (URL ou sessionStorage)

  • Validation du token au lancement de l'application

  • Redirection vers la mire d'authentification si besoin

    • token non présent

    • token invalide

    • token expiré

Authentification

  • Authentification des requêtes API

    • utilisation de l'access token renvoyé par l'OAuth

    • envoyé via un header HTTP

  • Détection des status code 401

    • redirect vers OAuth

 

API

REST ~ HATEOAS : attribut _links

GET /resource

 {

   "id": 1234,
   "firstname": "Jean",
   "lastname": "Dupont"






   
 }
GET /resource

 {

   "id": 1234,
   "firstname": "Jean",
   "lastname": "Dupont",

   "_links": {
     "factures": { "href": "...." },
     "lignes": { "href": "...." },
     "adressesFacturation": { "href": "...." }
   }
   
 }

API

  • Avantages

    • API auto-découvrante

    • Un seul point d'entrée : la ressource personne

  • Inconvénients

    • Beaucoup d'appels

    • Un appel par ressource

API

  • GET uniquement

    • Choix délibéré d'implémentation

  • POST/PUT/DELETE

    • Opérations réalisées sur l'espace client

API

  • Avantages

    • Cycle de livraison plus rapide

    • Impact réduit sur la sécurité

    • Suit le cycle de livraison des ressources

  • Inconvénients

    • Rediriger l'utilisateur vers l'espace client actuel

    • Trouver une solution pour l'expérience utilisateur

Templates

  • Utilisation de Handlebars

    • Compilation des templates avec Grunt

    • Utilisation du runtime dans le navigateur

    • Nombreux helpers métiers

    • Génération d'un seul fichier templates.js

Renderer

  • Customisation du Marionette.Renderer

  • Motivation :

    • utilisation de require.js

    • découplage vues / templates :

    • Utilisation d'une référence

// Vue avant
Mn.ItemView.extend({
    template: templates['offre-ligne']
});

// Vue avant
Mn.ItemView.extend({
    template: templates['offre-ligne']
});

// Vue après
Mn.ItemView.extend({
    template: 'offre-ligne'
});

Renderer

  • Implémentation simplifiée

var templates = require('templates');

Marionette.Renderer.render = function (name, data) {
    return this.renderString(name, data);
};

Marionette.Renderer.renderString = function (name, data) {
    if (!templates[name]) {
        throw new Error('template [' + name + '] not found.');
    }

    return templates[name](data);
};
var templates = require('templates');

Marionette.Renderer.render = function (name, data) {
    return this.renderString(name, data);
};

Bus Event

  • Centralisation de la distribution d'évènements

  • Meilleur découplage des différents composants/vues

 

  • Choix de Postal.js

    • Présence de channel

    • Wildcard (*, #) sur les topics

    • Utilisation d'une "enveloppe"

Bus Event

  • Cas d'utilisation : Backbone.history.navigate

handleLinkClick: function (e) {
    var $target = $(e.currentTarget);
    bus.publish('navigate', $target.attr('href'));
}
Marionette.AppRouter.extend({
    initialize: function () {
        this.subscriptions = [
            bus.subscribe('navigate', _.bind(this.handleNavigate, this))
        ];
    },

    handleNavigate: function (path) {
        // business logic ...
        this.navigate(path, {trigger: true});
    }
});

Monitoring

  • Génération de log depuis la SPA

    • Fonctionnel : Affichage des vues

    • Technique : Récupération des données

  • ​​Stockage dans un Elastic Search

  • Visualisation avec Kibana

Monitoring

Cohabitation ECCO / A360

  • Intégration d'ECCO dans A360

  • Sous la forme :

    • d'un lien vers une autre page 

    • ouverture d'une fenêtre (popup)

    • redirection vers une route interne

    • ouverture d'un bloc

 

Cohabitation ECCO / A360

Les liens vers l'espace client actuel (ECCO)

Cohabitation ECCO / A360

Page interne => IFrame

IFRAME

Cohabitation ECCO / A360

1

2

Injection d'un bloc HTML

=> IFrame

Cohabitation ECCO / A360

  • Problématique :

    • A360 est utilisé par différents profils

      • client

      • conseiller de clientèle (CDC)

      • conseiller de vente (CDV)

  • Problématique :

    • A360 est utilisé par différents profils

      • client

      • conseiller de clientèle (CDC)

      • conseiller de vente (CDV)

    • Chargement :

      • application web (client)

      • iframe dans salesforce (CDC)

      • webview dans application métier (CDV)

Cohabitation ECCO / A360

  • Lien vers ECCO dépend du profil de la personne

    • le lien

    • des paramètres à passer (query)

    • comportement d'affichage

      • iframe 

      • bloc

      • window

      • target (blank, self)
         

  • ​ECCO :

    • ~60 parcours utilisateurs à intégrer

Cohabitation ECCO / A360

  • Solution retenue

    • paramétrisation suivant le profil

    • paramétrisation des comportements
       

  • Utilisation de fichiers de configuration

    • gestion des paramètres

    • configuration des liens

    • configuration des routes internes

Paramétrisation du lien

  type:
    iframe:
      query: display=frame
      target: iframe
 

Définition des types:

 

- lien interne / externe

- query d'un lien

- target d'un élément A

- attribut DOM injecté 

 

  type:
    iframe:
      query: display=frame
      target: iframe
    newpage:
      target: _blank
      dataTarget: a360
 
  type:
    iframe:
      query: display=frame
      target: iframe
    newpage:
      target: _blank
      dataTarget: a360
    self:
      target: _self
      dataTarget: a360
  type:
    iframe:
      query: display=frame
      target: iframe
    newpage:
      target: _blank
      dataTarget: a360
    self:
      target: _self
      dataTarget: a360
    window:
      query: display=frame
      dataTarget: window
  type:
    iframe:
      query: display=frame
      target: iframe
    newpage:
      target: _blank
      dataTarget: a360
    self:
      target: _self
      dataTarget: a360
    window:
      query: display=frame
      dataTarget: window
    widget:
      query: display=frame
      dataTarget: widget

Paramétrisation du profil

  user:
    client:
      type: iframe

Comportement par défaut pour chaque profil 

 

  user:
    client:
      type: iframe
    cdc:
      type: window
      query: login=:login
  user:
    client:
      type: iframe
    cdc:
      type: window
      query: login=:login
    cdv:
      type: iframe
      query: login=:login

Exemple de surcharge

reinitialisercodeparental:
changementMDP:
reinitialisercodeparental:
  query: ctype=p&no_reference=:contractId
reinitialisercodeparental:
  query: ctype=p&no_reference=:contractId
  user:
    client:
changementMDP:
  user:
    cdc:
      query: type=interne
changementMDP:
  user:


    client:
      type: self
      query: CallbackURL=${url.a360}
changementMDP:
  user:
    cdc:
      query: type=interne
    client:
      type: self
      query: CallbackURL=${url.a360}
reinitialisercodeparental:
  query: ctype=p&no_reference=:contractId
  user:
    client:
      type: widget

Mise en place des liens

  • ~60 liens à mettre en place

    • interne (router)

    • externe 

  • 3 types de profils principaux

  • soit ~180 boutons ?

=> il doit y avoir une solution plus simple et plus "propre"

Mn.Behaviors

Utilisation d'un behaviour

<a {...}
    data-link-type="external"

>
    
    Changement mot de passe

</a>
<a {...}
    data-link-type="external"
    data-link="changementMDP"
>
    
    Changement mot de passe

</a>

Mise en place du lien dans le template

Utilisation d'un behaviour

<a {...}
    data-link-type="external"
    data-link="changementMDP"


>
    
    Changement mot de passe

</a>

Manipulation de la balise A par le behaviour 

<a {...}
    data-link-type="external"
    data-link="changementMDP"
    href="<link>?ctype=p&display=frame&ctype=p&no_reference=1234"
    target="_self"
>
    
    Changement mot de passe

</a>

Utilisation d'un behaviour

Marionette.ItemView.extend({
  behaviors: {



  },







});
Marionette.ItemView.extend({
  behaviors: {
    EccoButton: {
      behaviorClass: behaviors['ecco-button']
    }
  },







});
Marionette.ItemView.extend({
  behaviors: {
    EccoButton: {
      behaviorClass: behaviors['ecco-button']
    }
  },

  eccoData: function() {
    var contrat = this.options.contract;
    return {
      contractId: contract.id
    };
  }
});

Mise en place du behaviour

Utilisation d'un behaviour

<a 
    {...}
    data-link-type="external" 
    
    
>

    Réinitialiser code parental

</a>
<a 
    {...}
    data-link-type="external" 
    data-link="reinitialisercodeparental" 

>

    Réinitialiser code parental

</a>
<a 
    {...}
    data-link-type="external" 
    data-link="reinitialisercodeparental" 
    data-widget-target="reinitcode-widget"
>

    Réinitialiser code parental

</a>

<div data-widget="reinitcode-widget"></div>

IFrame

  • Injection d'une IFrame au travers d'une Mn.ItemView

  • Ajout des écouteurs sur les évènements :

    • onload

    • onresize

    • onmessage (et postMessage)

  • Non supporté par la propriété "events" d'une vue

Cohabitation ECCO / A360

"Sticky bar" 

Menu contextuel commun à tous les sites BouyguesTelecom

 

=> Réécriture du lien une fois la barre chargée

Sticky Bar

  • Injection d'un script pour l'affichage du menu

  • Nécessité de modifier le lien

 

  • Problème

    • comment s'assurer que l'HTML est bien injecté pour pouvoir modifier le lien 

  • Solution

    • MutationObserver

MEP 1.0 (Beta)

  • Début octobre

  • Ouverture progressive

    • CDC / CDV

    • 3% des clients

  • Fin prévisionnelle de la beta en janvier 2016

  • Amélioration des performances 

    • attente fortement réduite sur A360

    • pré-chargement < 0.5s (A360) contre ~2s (ECCO)

    • moins de données échangées

    • navigation beaucoup plus fluide

Merci :)

ByTel

By stephane bachelier