Archives de catégorie : Salesforce

Raspberry Cars : RC 2.0 !

Pour mon travail personnel de l’année 2019-2020 au Lycée Ermesinde, je vais étendre mon travail personnel de l’année dernière, avec de nouvelles technologies pour le rendre plus performant et avancé.

Le travail personnel de l’année dernière consistait à connecter le site Salesforce avec une voiture Mercedes pour recevoir des donnés sur la voiture en fonctionnement. Par exemple la position de la voiture, sa vitesse, le niveau de carburant. Avec ces informations, on a eu l’idée de faire une course virtuelle où des personnes peuvent aller d’un point de départ à un point d’arrivée, à des dates différentes, et par la suite on peut comparer les trajets entre eux.

Le projet de l’année dernière avait comme limitations que les données passaient par les serveurs de Mercedes : Les données de la voiture étaient lues par une application Mercedes sur le téléphone mobile puis envoyée sur le serveur Mercedes ; Ensuite, un programme exécuté chez Salesforce allait se connecter automatiquement au serveur Mercedes pour demander les données de la voiture et les enregistrer dans la base de données de Salesforce.

En résumé, les limitations étaient les suivantes :

  • On avait besoin d’avoir le téléphone dans la voiture
  • Ceci fonctionnait juste sur les voitures Mercedes
  • Pour que Salesforce se connecte aux API du serveur cloud de Mercedes il fallait un contrat avec Mercedes et une fois la phase de développement terminée il faut payer un abonnement
  • On ne pouvait pas facilement se connecter très souvent du serveur Salesforce au serveur Mercedes (on a fait toutes les 10min.)
  • L’application Mercedes n’enregistrait pas les donnés à une haute fréquence, on voudrait enregistrer toute les 2-10 secondes par exemple pour avoir une course plus dynamique

Ce que je veux réaliser cette année c’est :

  • Un petit ordinateur autonome dans la voiture,
  • Fonctionnant avec toutes les marques de voiture,
  • Ne nécessitant pas d’abonnement particulier,
  • Enregistrant de la même manière la position de la voiture, la vitesse, le niveau de carburant, l’état des pneus et d’autres paramètres,
  • Les envoyant à Salesforce le plus souvent possible.

Ce travail personnel va donc nécessiter différents groupes de tâches :

Voici la suite d’articles décrivant les différentes étapes du projet  :

 

Le Jeu (2/2) : Afficher le déroulement de la compétition des voitures d’une course fictive

L’objectif du projet était de simuler une ‘course’ pour des voitures su un même trajet, mais roulant à des dates différentes.

Attaquons-nous à la dernière étape : la course !

1.         La définition de la course

Si les voitures ne partent pas à la même heure, on doit pouvoir trouver comment comparer leur avancée dans la course par rapport à leur date de départ.

Une course dans notre jeu sera d’aller d’une place à l’autre le plus vite possible.

On va créer un objet Car Race qui mémorise :

  • le nom de la course
  • le point de départ
  • le point d’arrivée

Et pour chaque participant, un objet Racer qui va mémoriser

  • la course
  • la voiture
  • la date/heure de début
  • la date/heure de fin
  • la position enregistrée de début (car status)
  • la position enregistrée de fin (car status)

Voici la configuration des deux objets dans l’Object Manager :

2.         Afficher la compétition

Maintenant nous pouvons comparer le parcours complet des participants même s’ils partent à des dates différentes. On va afficher avec un composant mis sur l’objet Course la position des participants à x minutes, x étant saisi par l’utilisateur (10’ après le départ fictif, 20’ après le départ, etc.).

a)              Le service coté serveur

On a besoin d’un code Apex qui va calculer la position des participants après x minutes de course.

public class CarRaceController {

@AuraEnabled(Cacheable=true)
public static Car_Status__c[] getRacePositions(ID raceID, Integer chronoInMinutes) {
// on retrouve la liste des participants
List<Racer__c> lRacers = [
SELECT
Id,
Car_Race__c,
Car__c,
Start_Date__c
FROM Racer__c
where Car_Race__c =:raceID
];

// le tableau où on stocke les position qu'on a trouvées.
List<Car_Status__c> knowPositions = new List<Car_Status__c>(); 

// pour chaque participant, on cherche sa dernière position à cette minute de la course
for (Racer__c racer : lRacers) {
// calcule l'heure pour se participant
DateTime positionTime =  racer.Start_Date__c;
positionTime = positionTime.addMinutes(chronoInMinutes);
// recherche la dernière position connue pour cette heure
List<Car_Status__c> carPosition = [
SELECT
Id,
Location__Latitude__s,
Location__Longitude__s,
Requested_On__c,
Car__c,
Car__r.Name
FROM Car_Status__c
WHERE Car__c = :racer.Car__c
AND Requested_On__c >= :racer.Start_Date__c
AND Requested_On__c <= :positionTime
ORDER BY Requested_On__c DESC
LIMIT 1
];

// si on a trouvé une position, on la met de coté
if (carPosition.size()>0) {
knowPositions.add(carPosition[0]);
}
}

// on renvoie les positions qu'on a trouvée
return knowPositions;
}

}

b)              Le composant

Comme d’habitude on dit où il doit s’afficher : sur les pages des objets Courses (Car Race).

<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="RaceCarPositionsMap">

<apiVersion>45.0</apiVersion>
<isExposed>true</isExposed>

<targets>
<target>lightning__RecordPage</target>
</targets>

<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Car_Race__c</object>
</objects>

</targetConfig>
</targetConfigs>

</LightningComponentBundle>

La partie Html est un peu plus compliquée : elle a un champ inputMinutes pour saisir le nombre de minutes que l’on veut. Quand ce champ est modifié avec la valeur x, le code javascript minutesChangeHandler est appelé pour demander à Salesforce les positions à x minutes. La carte fonctionne sur le même mode que celle qu’on avait faite dans la partie précédente : elle affiche les marqueurs mapMarkers.

<template>
<article class="slds-card">

<div class="slds-m-around_medium">
<p>Race Id at {recordId}</p>
<p>Race Info of {raceName}</p>
<p>Position at {currentRaceMinutes}</p>
</div>

<lightning-input
name="inputMinutes" label="Minutes"
type="number"
value={currentRaceMinutes}
step="5" max="1000" min="0"
onchange={minutesChangeHandler}>
</lightning-input>

<div class="slds-m-around_medium">
<lightning-map
map-markers={mapMarkers}
zoom-level="11"
markers-title="Positions"
>
</lightning-map>
</div>
</article>
</template>

Le code Java script est fait sur le même modèle que le précédent, et va charger les positions des voitures à x minutes en appelant le code Apex getRacePositions avec l’id de la course et le nombre de minutes après le départ.

On fait le mapping deux fois :

  • une fois quand la page se charge
@wire(getRacePositions, { raceID :  '$recordId',  chronoInMinutes : '$currentRaceMinutes'  })
  • et une fois quand les minutes sont changées.
getRacePositions( { raceID :  this.recordId,  chronoInMinutes : this.currentRaceMinutes})

Le code pour créer les marqueurs ressemble à celui du composant précédent

this.mapMarkers = result.data.map(carPosition => {
const CarName = carPosition.Car__r.Name;
const Latitude = carPosition.Location__Latitude__s;
const Longitude = carPosition.Location__Longitude__s;

return {
location: { Latitude, Longitude },
title: CarName,
description: 'Coords: ${Latitude}, ${Longitude}',
icon: 'utility:animal_and_nature'
};

});

Voici le code complet javascript :

import { LightningElement, api, wire, track } from 'lwc';
import { getRecord , getFieldValue  } from 'lightning/uiRecordApi';
import getRacePositions from '@salesforce/apex/CarRaceController.getRacePositions';
import RACE_NAME from '@salesforce/schema/Car_Race__c.Name';
const raceFields = [
RACE_NAME
];

export default class RaceCarPositionsMap extends LightningElement {
@api recordId;
@wire(getRecord, { recordId: '$recordId', fields: raceFields })
raceInformation;

// les minutes où on se trouve (30 pour le test)
@track currentRaceMinutes = 0;

// les marqueurs pour la carte
@track mapMarkers = [];

 // les positions
@track carsPositions;

get raceName() {
return getFieldValue(this.raceInformation.data, RACE_NAME);
}

@wire(getRacePositions, { raceID :  '$recordId',  chronoInMinutes : '$currentRaceMinutes'  })
loadCar(result) {
if (result.data) {
this.carsPositions = result.data;
this.mapMarkers = result.data.map(carPosition => {
const CarName = carPosition.Car__r.Name;
const Latitude = carPosition.Location__Latitude__s;
const Longitude = carPosition.Location__Longitude__s;
return {
location: { Latitude, Longitude },
title: CarName,
description: 'Coords: ${Latitude}, ${Longitude}',
icon: 'utility:animal_and_nature'
};
});
}
}

minutesChangeHandler(event) {
this.currentRaceMinutes = event.target.value;
this.refreshMapForNewMinutes();
}

refreshMapForNewMinutes() {
getRacePositions( { raceID :  this.recordId,  chronoInMinutes : this.currentRaceMinutes})
.then(result => {
if (result.data) {
this.carsPositions = result.data;
this.mapMarkers = result.data.map(carPosition => {
const CarName = carPosition.Car__r.Name;
const Latitude = carPosition.Location__Latitude__s;
const Longitude = carPosition.Location__Longitude__s;
return {
location: { Latitude, Longitude },
title: CarName,
description: 'Coords: ${Latitude}, ${Longitude}',
icon: 'utility:animal_and_nature'
};
});
}
})
.catch(error => {
alert('Error '+JSON.stringify(error));
this.checkIfTimeEntryCanBeCreated();
});
}
}

 

c)              Le résultat

Une fois le composant installé dans la page des courses, voici ce que cela donne pour différentes minutes de course (85’, 90’).

Maintenant si on fait bouger les minutes, on voit les marqueurs des voitures se déplacer ! C’était l’objectif de cette partie ‘course virtuelle’ !

B.        Création de l’application Car Races

1.         Configuration

Pour accéder facilement à tous les écrans et objets de notre jeu, on peut créer « une application ».

Cela permet aux utilisateurs d’arriver plus facilement aux écrans concernant les voitures, la connexion, les courses.

Dans le menu Setup on choisit : App / App Manager puis on clique « New Lightning App ». On lui donne un nom et on choisit ce qu’elle doit afficher.

2.         Utilisation

Pour aller dans la nouvelle application, on clique en haut à gauche sur l’icône avec les neuf petits carrés, on choisit « Car Races » …

… et quand on est dans l’application, on ne voit dans la barre d’onglets que les objets configurés. C’est plus facile pour s’y retrouver.

Par exemple pour l’accès direct à la liste des voitures

Ou voir la carte des voitures avec leurs dernières positions enregistrées sur la home page

Ou encore … … voir une course !

 

Le Jeu (1/2) : Afficher la position des voitures en temps réel

Pour cette première partie du jeu, on va créer un composant qui va afficher dans une page Salesforce toutes les voitures sur une carte. A chaque fois qu’on regarde la carte, on voit si la voiture a bougé.

Pour cela je me suis inspiré du code de l’application « Ours » du cours Salesforce qui affichait une carte des Ours d’un Parc Naturel.

2.         Le composant

Comme avant, on doit faire un fichier de description pour dire où le composant s’affiche

<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="CarInfo">
<apiVersion>45.0</apiVersion>

<isExposed>true</isExposed>

<targets>
<target>lightning__RecordPage</target>
</targets>

<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Car__c</object>
</objects>
</targetConfig>
</targetConfigs>

</LightningComponentBundle>

Le code HTML du composant est simple car il y a dans salesforce un composant tout prêt : lightning-map, à qui on donne une liste d’objet à placer sur la carte : mapMarkers.

<template>

<article class="slds-card">
<lightning-map
map-markers={mapMarkers}
zoom-level="11"
markers-title="Cars">
</lightning-map>
</article>

</template>

Le code javascript du composant va juste

  • charger les voitures depuis salesforce en appelant un code Apex : getAllCars
  • transformer les voitures en marqueurs pour la carte : data.map
import { LightningElement, track, wire } from 'lwc';
import getAllCars from '@salesforce/apex/CarController.getAllCars';

export default class CarMap extends LightningElement {

@track mapMarkers = [];
@track cars;

@wire(getAllCars, {})
loadCars(result) {
if (result.data) {
this.cars = result.data;
this.mapMarkers = result.data.map(car => {
const Latitude = car.Last_Position__Latitude__s;
const Longitude = car.Last_Position__Longitude__s;
return {
location: { Latitude, Longitude },
title: car.Name,
description: 'Coords: ${Latitude}, ${Longitude}',
icon: 'utility:animal_and_nature'
};
});
}

}}

Le code APEX qui renvoie les voitures est juste une requête SOQL vers la base Salesforce pour savoir la dernière position des voitures.

@AuraEnabled(Cacheable=true)

public static Car__c[] getAllCars() {
return [SELECT Id, Name,
Last_Position__longitude__s, Last_Position__latitude__s
FROM Car__c
WHERE Last_Position__longitude__s!= null
AND Last_Position__latitude__s != null
ORDER BY Name LIMIT 50
];
}
}

3.         Le résultat

Et voici ce que donne ce composant une fois installé dans une page Salesforce :

On sait où sont les voitures au fur et à mesure que salesforce récupère les informations depuis le serveur Cloud Mercedes !

 

Le module de gestion de la connexion de Salesforce vers Mercedes Cloud (3/3) : La récupération des informations des voitures

1.         Utilisation pour stocker / mettre à jour la liste des cars dans SF

a)              Un objet en mémoire pour récupérer le contenu de la réponse du serveur

Quand on demandait la voiture à la main avec Curl, on obtenait la réponse suivante

[
{
"id":                "00338353A46599799B",
"licenseplate":     "S-GG-9041",
"finorvin":         "1HM8CE2A6H6CEC802"
}
]

C’est un texte qui décrit la liste de voitures. Dans ce cas une seule voiture.

Pour pouvoir la manipuler, il faut définir un objet en mémoire dans Salesforce pour Apex.

class Vehicule {
public String salesforceId        { get; set; }
public String vehiculeId { get; set; }
public String licenseplate       { get; set; }
public String finorvin           { get; set; }
}

Et on va transformer chaque voiture de la liste dans un de ses objets.

b)              Le code qui analyse le contenu de la réponse du serveur et crée les voitures en mémoire

Du coup on va utiliser l’outil de Salesforce qui sert à lire un message JSON

JSON.deserializeUntyped( leMessage );

Et stocker en mémoire les voitures dans une liste

public List<Vehicule> vehicules { get; set; }

Comme pour l’appel à l’API précédente (session), on peut maintenant écrire le code qui appelle Mercedes, analyse la réponse et mémorise les voitures

public void loadVehiculesFromServer() {
  • Préparation de la requête
//Set HTTPRequest Method
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setHeader('authorization', 'Bearer '+access_token );
req.setHeader('accept', 'application/json');
req.setEndpoint(API_URL_Vehicules);
  • Appel de la requête
//Execute web service call here
Http http = new Http();
HTTPResponse res = http.send(req);
  • Récupération de la réponse
vehiculesJson = res.getBody();
System.debug('response:' + vehiculesJson);
  • Décodage de la réponse
List<Object> results =
(List<Object>)JSON.deserializeUntyped(vehiculesJson);

vehicules = new List<Vehicule>();

for(Object o:results)  {
Map<String,Object> m = (Map<String,Object>)o;
Vehicule v = new Vehicule();
v.vehiculeId = (String) m.get('id');
v.licenseplate = (String)  m.get('licenseplate');
v.finorvin =   (String) m.get('finorvin');
vehicules.add(v);
}
}

c)              Le code qui met à jour / crée les voitures dans la base SF à partir des voitures en mémoire

 

Ce qu’on veut maintenant c’est d’enregistrer les voitures qu’on vient d’obtenir dans Salesforce (mais sans créer des double à chaque appel). Pour ça on doit vérifier si la voiture est déjà là ou pas.

Cela se fait avec un programme qui  :

  1. lit les id Mercedes des voitures retournées par l’api Mercedes (ce qu’on vient de coder)
  2. charge les voitures de la base SF avec ces ID Mercedes pour cette connexion, et les place dans une carte
  3. pour chaque voiture retournée par l’API Mercedes
    1. regarde si elle est déjà dans la base salesforce
    2. sinon ; il prépare la création
    3. si oui ; il prépare la mise à jour des données dans la base salesforce
  4. il met à jour toutes les voitures qui existaient déjà
  5. il crée toutes celles qui n’existaient pas
public void storeVehiculesInSalesforce() {

//  Etape 1
Set<String> vehiculeMBIds = new Set<String>();
for (Vehicule v:vehicules) {
vehiculeMBIds.add(v.vehiculeId);
}

//  Etape 2
List<Car__c> listSFCars = [
SELECT Id, Mercedes_ID__c, Mercedes_API_Connection__c, Car_number__c
FROM Car__c
WHERE   Mercedes_API_Connection__c = :salesforceConnectionId
AND Mercedes_ID__c in :vehiculeMBIds
];

Map<String,Car__c> mapSFCars = new Map<String,Car__c>();
for (Car__c c:listSFCars) {
mapSFCars.put(c.Mercedes_ID__c, c);
}

//  Etape 3
List<Car__c> carsToCreate = new List<Car__c>();
List<Car__c> carsToUpdate = new List<Car__c>();

for (Vehicule v:vehicules) {
//  Etape 3a
Car__c theSFCar =  mapSFCars.get(v.vehiculeId);

if (theSFCar == null) {

//  Etape 3b
carsToCreate.add(
new Car__c(
Mercedes_API_Connection__c = salesforceConnectionId,
Mercedes_ID__c = v.vehiculeId,
Car_number__c = v.licenseplate
)
);

} else {

//  Etape 3c
theSFCar.Car_number__c = v.licenseplate;
carsToUpdate.add(theSFCar);
}

}

insert carsToCreate;
update carsToUpdate;

}

Maintenant à chaque fois qu’on appelle ce code, la liste des voitures dans Salesforce est mise à jour avec la réponse de Mercedes.

2.         Utilisation pour demander la position de toutes les voitures et les stocker dans la base SF

On va faire la même chose pour l’appel qui demande à Salesforce où se trouve chaque voiture.

a)              La réponse du serveur

En Curl, on obtenait la réponse suivante

{
"longitude": {"value":11.372609,"retrievalstatus":"VALID","timestamp":1548425864},
"latitude":  {"value":47.90321,"retrievalstatus":"VALID","timestamp":1548425864},
"heading":   {"value":52.520008,"retrievalstatus":"VALID","timestamp":1548425864}
}

b)              Un objet en mémoire pour récupérer le contenu de la réponse du serveur

On va aussi créer un objet Apex pour récupérer le résultat

class VehiculeLocation {
DateTime requestedOn { get; set; }
Decimal longitude    { get; set; }
Decimal latitude     { get; set; }
Decimal heading { get; set; }
}

c)              Un objet dans SF pour stocker la position

A chaque fois qu’on appelle la demande de position, on veut stocker cela comme enregistrement en plus. On a besoin de créer un nouvel objet dans Salesforce.

On l’appelle « Car Status », et il a les champs Position, Direction et date d’observation.

Voici ce que cela donne dans l’Object Manager.

d)              Des champs sur la voiture pour mémoriser la dernière position

On va en profiter aussi pour enregistrer la dernière position de la voiture dans l’objet voiture lui-même : un champ « Last Position ».

e)              Le code qui analyse le contenu de la réponse du serveur

Comme auparavant, on va appeler la requête et analyser le résultat. En rouge ce qui est particulier pour ce cas.

public VehiculeLocation getVehiculeLocation(String aVehiculeMbId) {

//Set HTTPRequest Method
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setHeader('authorization', 'Bearer '+access_token );
req.setHeader('accept', 'application/json');
req.setEndpoint(API_URL_Vehicules+'/'+aVehiculeMbId+'/location');

//Execute web service call here
Http http = new Http();
HTTPResponse res = http.send(req);

//Helpful debug messages
String jsonResponse = res.getBody();
System.debug('response:' + jsonResponse);

Map<String, Object> results =(Map<String, Object>)JSON.deserializeUntyped(jsonResponse);

VehiculeLocation vl = new VehiculeLocation();
vl.requestedOn = System.now();
vl.longitude= (Decimal) ((Map<String, Object>) results.get('longitude')).get('value');
vl.latitude= (Decimal) ((Map<String, Object>) results.get('latitude')).get('value');
vl.heading=(Decimal) ((Map<String, Object>) results.get('heading')).get('value');
System.debug('pos:'+vl);

return vl;
}

f)               Le code qui stocke dans la base SF les positions des voitures récupérées

Comme pour la liste des voitures, on va enregistrer les positions obtenues.

On va donc écrire un programme qui

  1. pour chaque véhicule
    1. appelle le serveur pour obtenir la position (le code du paragraphe précèdent)
    2. retrouve la voiture dans la base SF
    3. crée en mémoire l’objet Car_Status__c à stocker dans SF
    4. mémorise sur la voiture sa dernière position
  2. enregistre tous les objets Car_Status__c en une fois
  3. met à jour toutes les voitures (nouvelles positions) en une fois

public void getAndStoreVehiculeLocations() {

list<Car_Status__c> statusToInsert = new list<Car_Status__c>();

//  Etape 1
for (Vehicule v:vehicules) {

//  Etape 1a
VehiculeLocation vl = getVehiculeLocation(v.vehiculeId);

// Etape 1b
Car__c theCar = [
SELECT
Id,
Mercedes_ID__c,
Mercedes_API_Connection__c,
Car_number__c
FROM Car__c
WHERE Mercedes_API_Connection__c = :salesforceConnectionId
AND Mercedes_ID__c = :v.vehiculeId
];

// Etape 1c
statusToInsert.add(
new Car_Status__c(
Car__c = theCar.id,
Heading__c = vl.heading,
Location__longitude__s = vl.longitude,
Location__latitude__s = vl.latitude,
Requested_On__c = vl.requestedOn
)
);

// Etape 1d
theCar.Last_Position__longitude__s = vl.longitude;
theCar.Last_Position__latitude__s = vl.latitude;
carsToUpdate.add(theCar);
}

// Etape 2
insert statusToInsert;

// Etape 3
update(carsToUpdate);

}

3.         Enregistrer automatiquement toutes les 5 ‘ la position de la voiture

Si on veut suivre les voitures en permanence, il faut répéter l’enregistrement de la position régulièrement.

Ceci est le code pour une seule connexion qu’on veut exécuter toutes les 5 minutes (le code Apex qu’on a écrit avant, dans la classe Mercedes API, avec ‘a011t00000ByCf5AAF’ qui est l’identifiant de l’objet pour la connexion à Mercedes) :

MercedesAPI api = new MercedesAPI('a011t00000ByCf5AAF');
api.loadVehiculesFromServer();
api.getAndStoreVehiculeLocations();

Pour cela, on doit le placer dans une classe Apex programmable qui :

  1. Charge la liste des connexions, et pour chacune
  2. Fait les appels pour la liste des positions
global class MercedesStatusRequestScheduler implements Schedulable {

global void execute(SchedulableContext SC) {
//  Etape 1
for (Mercedes_API_Connection__c c : [SELECT Id FROM Mercedes_API_Connection__c]) {
//  Etape 2
doRequest(c.id);
}

}

    @future(callout=true)
    public static void doRequest(String theConnectionId) {
       MercedesAPI api = new MercedesAPI(theid);
       api.loadVehiculesFromServer();
       api.getAndStoreVehiculeLocations();
   }

}

Pour chaque exécution souhaitée (toutes les 5 ‘ d’une heure, donc 12 fois), on doit enregistrer l’exécution dans le tableau d’exécution SF.

Voici le code qui permet d’enregistrer une exécution

public static void schedule(String name, String cron) {
MercedesStatusRequestScheduler leJob =
new MercedesStatusRequestScheduler();
String jobID = system.schedule(name,cron,leJob);
}

Pour le programmer toutes les 5’ on doit enregistrer 12 programmations ainsi

schedule('Mercedes-Status xh00m', '0 0 * * * ? *');
schedule('Mercedes-Status xh05m', '0 5 * * * ? *');
schedule('Mercedes-Status xh10m', '0 10 * * * ? *');
schedule('Mercedes-Status xh15m', '0 15 * * * ? *');
schedule('Mercedes-Status xh20m', '0 20 * * * ? *');
schedule('Mercedes-Status xh25m', '0 25 * * * ? *');
schedule('Mercedes-Status xh30m', '0 30 * * * ? *');
schedule('Mercedes-Status xh35m', '0 35 * * * ? *');
schedule('Mercedes-Status xh40m', '0 40 * * * ? *');
schedule('Mercedes-Status xh45m', '0 45 * * * ? *');
schedule('Mercedes-Status xh50m', '0 50 * * * ? *');
schedule('Mercedes-Status xh55m', '0 55 * * * ? *');

4.         Amélioration : indiquer quelle connexion ou quelle voiture on veut suivre

Pour le moment, on ne peut pas activer désactiver l’enregistrement des positions.

Pour décider à chaque exécution :

  • si on veut charger les voitures d’une connexion
  • quelle voiture on veut interroger

On ajoute une checkbox :

  • à l’objet custom connection : Scheduled_Job_Execution__c
  • à l’objet custom car : Record_Status__c

Et on modifie le code ainsi

  • Quand on charge l’objet de connexion (Mercedes API connection) en mémoire, on mémorise si on doit faire l’enregistrement des positions
public boolean scheduledJobsExecution {get; set;  }
public MercedesAPI(Id aSalesforceConnectionId) {
…

scheduledJobsExecution = theConnection.Scheduled_Job_Execution__c;
}
  • Dans le code programmé toutes les 5 minutes, on vérifie si la connexion doit être enregistrée
@future(callout=true)
public static void doRequest(String theid) {
MercedesAPI api = new MercedesAPI(theid);
if (api.scheduledJobsExecution) {
api.loadVehiculesFromServer();
api.getAndStoreVehiculeLocations();
}
}

Dans le code d’interrogation et de stockage des statuts, on vérifie si la voiture doit être suivie pour sa position

public void getAndStoreVehiculeLocations() {
list<Car_Status__c> statusToInsert = new list<Car_Status__c>();
for (Vehicule v:vehicules) {
Car__c theCar = [
SELECT
Id,
Mercedes_ID__c,
Mercedes_API_Connection__c,
Car_number__c,
Record_Status__c
FROM Car__c
WHERE Mercedes_API_Connection__c = :salesforceConnectionId
AND Mercedes_ID__c = :v.vehiculeId
];
 
             if (theCar.Record_Status__c==true) {
VehiculeLocation vl = getVehiculeLocation(v.vehiculeId);
statusToInsert.add(new Car_Status__c(
Car__c = theCar.id,
Heading__c = vl.heading,
Location__longitude__s = vl.longitude,
Location__latitude__s = vl.latitude,
Requested_On__c = vl.requestedOn
)); 
            }

}

insert statusToInsert;
}

 

Et voilà maintenant, quand le code s’exécute, on n’accède à la connexion et on n’appelle l’enregistrement des voitures que si les cases sont cochées pour cette connexion et cette voiture.

Le module de gestion de la connexion de Salesforce vers Mercedes Cloud (2/3) : La connexion

Dans les Posts précédents, nous avons examiné comment appeler le serveur Mercedes Cloud ‘à la main’ dans la console de programmation Salesforce. Le but de ce Post est d’expliquer comment implémenter le code de manière à ce qu’il soit utilisable par un utilisateur normal.

1.         Un nouvel objet pour stocker les infos de connexion

On vient de travailler ‘à la main’ dans la console. Mais si on veut que ce soit facile de travailler avec tous ces codes, ce serait bien de les enregistrer quelque part.

Pour cela on va créer un objet SF qui sert à mémoriser les informations de la connexion vers Mercedes, on a besoin de :

  • client id =txt
  • client secret=txt
  • authorisation code=txt
  • access token=txt
  • expiration date = date/time
  • scope = texte
  • refresh token = texte

On va créer les champs de cet objet connexion avec le menu setup, comme on avait fait pour l’objet voiture.

On peut du coup saisir les informations de connexion dans Salesforce

Et aussi faire les requêtes avec du code SOQL comme on l’a fait avant pour les voitures :

SELECT Id, Mercedes_API_Client_ID__c, Mercedes_API_Client_Secret__c FROM Mercedes_API_Connection__c

2.         Comment savoir pour quelle connexion on a demandé l’autorisation ?

Quand le serveur Mercedes renvoie vers la page SF, on ne peut pas passer de paramètre. Il n’est pas possible de savoir quelle Mercedes Connection a été autorisée s’il y en a plusieurs configurées dans SF.

On va donc devoir « marquer » dans la base SF quelle connexion est concernée avant l’appel de la page d’autorisation Mercedes, pour que quand l’utilisateur revient dans SF sur la page d’après-autorisation on retrouve la connexion concernée.

On a ajouté deux champs à l’objet Mercedes Connection :

  • Last_Autorisation_Request_Time__c : la dernière fois qu’un utilisateur a demandé l’autorisation
  • Last_Autorisation_Request_User__c : quel utilisateur a demandé l’autorisation

Avant d’aller vers la page d’autorisation Mercedes, on va marquer la connexion ainsi en mémorisant que la connexion vient d’être demandée par l’utilisateur et que cela vient de se faire.

3.         Page pour demander la connexion

On va créer un composant visuel qui sera placé sur les objets Mercedes Api Connection.

Ce composant va permettre de cliquer sur un bouton pour aller sur le serveur Mercedes pour activer la connexion.

a)              Le composant visuel

C’est un simple composant qui affiche l’Id de la connexion et un bouton pour appeler la page de connexion

<template>

<lightning-card title="Connection Information" icon-name="standard:people">
<div class="slds-m-around_medium">
<p>The connection is </p>
<ul>
<li>SF ID : {recordId}</li>
</ul>
 
</div>
<lightning-button label="Activate Connection" onclick={handleActivateConnection} variant="brand"></lightning-button>
</lightning-card>
 
</template>

b)              Le code javascript du composant

Dans le code javascript du composant, ce qu’il y a de particulier est que :

  • Le code handleActivateConnection appelle d’abord une méthode memorizeAutorisationRequestContext du serveur (Apex) pour stocker le fait qu’on vient de lancer le processus pour cette connexion (voir paragraphe suivant)
memorizeAutorisationRequestContext( {aConnectionId :  this.recordId })
  • Quand cela a marché, dit d’aller vers la page de connexion Mercedes (l’URL est calculée par le code coté serveur) :
this[NavigationMixin.Navigate]({
"type" : "standard__webPage",
attributes: {
"url" : this.autorisationPageURL.data
}
}

Cela donne le code suivant pour le javascript du composant :

import { LightningElement, api, wire } from 'lwc';
import { getRecord /*, getFieldValue */ } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { NavigationMixin } from 'lightning/navigation';

import memorizeAutorisationRequestContext from '@salesforce/apex/MercedesAPI.memorizeAutorisationRequestContext';

import getAuthorizationPageURL from '@salesforce/apex/MercedesAPI.getAuthorizationPageURL';

import CONNECTION_SALEFORCE_ID from '@salesforce/schema/Mercedes_API_Connection__c.Id';

import CONNECTION_CLIENT_ID from '@salesforce/schema/Mercedes_API_Connection__c.Mercedes_API_Client_ID__c';

import CONNECTION_CLIENT_SECRET from '@salesforce/schema/Mercedes_API_Connection__c.Mercedes_API_Client_Secret__c';

import CONNECTION_ACCESS_TOKEN from '@salesforce/schema/Mercedes_API_Connection__c.Mercedes_API_Acces_Token__c';


const connectionFields = [
CONNECTION_SALEFORCE_ID,
CONNECTION_CLIENT_ID,
CONNECTION_CLIENT_SECRET,
CONNECTION_ACCESS_TOKEN
];

export default class MercedesConnectionManager extends NavigationMixin(LightningElement)
{

@api recordId; // Connection Id
@wire(getRecord, { recordId: '$recordId', fields: connectionFields })
mercedesConnection;

@wire(getAuthorizationPageURL, {  aConnectionId: '$recordId' })
autorisationPageURL;
get urlInfos() {
return JSON.stringify(this.autorisationPageURL);
}

handleActivateConnection() {
memorizeAutorisationRequestContext( {aConnectionId :  this.recordId })
.then(result => {
// small event message
const evt = new ShowToastEvent({
title: 'Connection Context Memorized',
message: 'The connection context has been successfully memorized for '+result,
variant: 'success',
});
this.dispatchEvent(evt);
// go to page
this[NavigationMixin.Navigate]({
"type" : "standard__webPage",
attributes: {
"url" : this.autorisationPageURL.data
}
},
{
replace: true
}
);
})
.catch(error => {
const evt = new ShowToastEvent({
title: 'Connection Context Not Memorized',
message: 'An error occured '+JSON.stringify(error),
variant: 'error',
});
this.dispatchEvent(evt);
});
}
}

c)              Le code Apex

C’est le code du côté Salesforce qui fait les calculs et les sauvegarde (dans la classe MercedesAPI).

Avant d’aller vers la page d’autorisation Mercedes, on marque la connexion ainsi :

@AuraEnabled(cacheable=false)
public static id memorizeAutorisationRequestContext(Id aConnectionId) {
// recupere la connection
Mercedes_API_Connection__c theConnection = [
SELECT
Id,
Name,
Last_Autorisation_Request_User__c,
Last_Autorisation_Request_Time__c
FROM Mercedes_API_Connection__c
where id = :aConnectionId
];

// memorise qui et quand
theConnection.Last_Autorisation_Request_User__c = UserInfo.getUserId();
theConnection.Last_Autorisation_Request_Time__c = System.now();

// sauve le resultat
update theConnection;

return aConnectionId;
}

Et le code qui calcule la page pour demander l’autorisation d’accès à Mercedes

@AuraEnabled(cacheable=true)
public static String getAuthorizationPageURL(Id aConnectionId) {
Mercedes_API_Connection__c myConnection = [
SELECT
Id,
Mercedes_API_Client_ID__c
FROM Mercedes_API_Connection__c
WHERE id = :aConnectionId
];

return API_URL_Authorization
+ '?response_type=code'
+ '&client_id=' + myConnection.Mercedes_API_Client_ID__c
+ '&redirect_uri='+redirectURI
+ '&scope='+requestedScope;

}

d)              Les informations du composant pour indiquer qu’il est affichable sur les pages ‘Mercedes Api Connection’

<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="mercedesConnectionManager">

<apiVersion>45.0</apiVersion>

<isExposed>true</isExposed>

<targets>
<target>lightning__RecordPage</target>
</targets>

<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Mercedes_API_Connection__c</object>
</objects>
</targetConfig>

</targetConfigs>

</LightningComponentBundle>

e)              Le composant ajouté à la page de l’objet API Connection

On ajoute le composant à la page comme on avait fait avant pour le composant qui affichait la couleur d’une voiture.

4.         Page qui récupère les informations de demande d’autorisation

Quand on clique sur le lien, on arrive chez Mercedes où on peut valider qu’on autorise l’accès, puis Mercedes redirige sur la page Mercedes API Authorization (ce qu’on avait fait dans la partie d’avant)

Il faut maintenant faire que cette page utilise le code d’autorisation qu’elle a récupéré

a)              La page

Pour cela on ajoute un bouton sur la page

<apex:page controller="Mercedes_API_Authorization_Ctrl" >
Authorization : {! authorizationCode}
<apex:form >
  <apex:commandButton value="validate connection request" action="{!saveAuthorizationCode}" />
</apex:form>
</apex:page>

Le bouton appelle une action saveAuthorizationCode dans le contrôleur de la page (Mercedes_API_Authorization_Ctrl )

b)              Le code Apex

Du coup, quand on revient dans l’environnement SF de l’utilisateur on peut retrouver la bonne connexion pour y sauver les informations de session (token, etc.) : on prend la dernière connexion modifiée par cet utilisateur

public PageReference saveAuthorizationCode() {

mercedesConnection = [
SELECT
Id,
Name,
Mercedes_API_Client_ID__c,
Mercedes_API_Client_Secret__c,
Mercedes_API_Autorization_Code__c
FROM Mercedes_API_Connection__c
where
Last_Autorisation_Request_User__c= : UserInfo.getUserId()
order by Last_Autorisation_Request_Time__c desc
limit 1
];

….

}
  1. c) On utilise le code de connexion qu’on avait auparavant recopié dans une classe MercedesAPI pour pouvoir le réutiliser.
MercedesAPI api = new MercedesAPI(
theConnection.Mercedes_API_Client_ID__c,
theConnection.Mercedes_API_Client_Secret__c
);
api.initSession(authorizationCode);
  1. d) On stocke l’autorisation et les tokens obtenus
theConnection.Mercedes_API_Autorization_Code__c = authorizationCode;
theConnection.Mercedes_API_Acces_Token__c = api.access_token;
// sauver les autres champs ici
update theConnection;

Et voilà la connexion est établie !

Voici le code avec tous les morceaux mis ensemble.

 
public PageReference saveAuthorizationCode() {

Mercedes_API_Connection__c theConnection = [
SELECT
Id,
Mercedes_API_Client_ID__c,
Mercedes_API_Client_Secret__c,
Mercedes_API_Autorization_Code__c
FROM Mercedes_API_Connection__c
// where id = :monID
limit 1
];

MercedesAPI api = new MercedesAPI(
theConnection.Mercedes_API_Client_ID__c,
theConnection.Mercedes_API_Client_Secret__c
);

api.initSession(authorizationCode);
api.getVehicules();

theConnection.Mercedes_API_Autorization_Code__c = authorizationCode;
theConnection.Mercedes_API_Acces_Token__c = api.access_token;

// sauver les autres champs ici
update theConnection;

vehiculeTexte = api.vehiculesJson;
return null;
}

Et on peut demander à rafraîchir cette partie de page après le clic sur le bouton

<apex:commandButton value="validate connection request" action="{!saveAuthorizationCode}" reRender="vehiculesPanel"/>

Voici ce qu’on voit quand on arrive sur la page ‘redirect url’ après être passé sur le serveur Mercedes :

Puis ce qu’on voit quand on clique sur ‘Validate’

La connexion est maintenant active et le token de session est enregistré.

5.         Rafraichissement automatique du token de connexion

Normalement, le jeton de session n’est valide qu’une heure. Il faudrait faire un code qui appel l’API pour rafraîchir automatiquement le token toutes les heures.

Je ne l’ai pas fait dans le temps de mon Trape.

C’est une amélioration à faire. Pour le moment, il faut recliquer sur ces écrans toutes les heures.

Le module de gestion de la connexion de Salesforce vers Mercedes Cloud (1/3) : Techniques utilisées

Dans les deux premières parties nous avons parlé de l’API Mercedes et on l’a utilisée ‘à la main’ avec CuRL en ligne de commande, puis on a regardé comment utiliser et configurer Salesforce,  puis  programmer des composants visuels avec Salesforce et enfin comment stocker et lire en mode programmation des données dans Salesforce.

Maintenant, on regarde comment programmer ces API Mercedes que l’on a manipulé en CuRL avec les outils de programmation de Salesforce.

1.         La page qui permet de recevoir l’autorisation

Quand on appelé le mécanisme de Mercedes pour obtenir le code d’autorisation, on allait sur une URL Mercedes avec nos données de l’application (dont le client id) , c’était l’étape A ; puis l’utilisateur tapait ses données de connexion pour autoriser l’application (étape B) ; et enfin Mercedes renvoyait sur une page de l’application en passant le code d’autorisation en paramètre (étape C).

L’URL de l’étape C ressemblait à ceci, avec le code obtenu en rouge :

https://localhost/?code=6e9d89c4-a374-4bb9-8c88-673034f87342

Parce qu’on avait dit que la redirect URL de l’application était https://localhost/.

En Salesforce on a besoin de faire une page web qui va savoir lire le paramètre code pour qu’on puisse l’utiliser par la suite.

Le code de la page est simple. Ce sera une page Visual Force, vide pour le moment

<apex:page controller="Mercedes_API_Authorization_Ctrl">
</apex:page>

Avec un controller Mercedes_API_Authorization_Ctrl qui :

  • Stocke le code d’autorisation (en bleu)
  • Lit le paramètre passé quand la page se charge. (en orange).
public class Mercedes_API_Authorization_Ctrl {
    public String authorizationCode { get; set; }
 
public Mercedes_API_Authorization_Ctrl(){
   authorizationCode =
apexpages.currentpage().getparameters().get('code');
}
}

Pour faire plus visible, on peut aussi modifier la page pour qu’elle affiche le paramètre obtenu

<apex:page controller= »Mercedes_API_Authorization_Ctrl »>
  Authorization : {! authorizationCode}
</apex:page>

 

On doit ajouter cette redirect URL à l’application déclarée chez Mercedes :

Essayons d’ouvrir la première URL (etape A), avec la bonne redirect URL (en vert), puis suivons les étapes, on arrive à :

https://api.secure.mercedes-benz.com/oidc10/auth/oauth/v2/authorize?response_type=code&client_id=36f504aa-f141-41ad-93ae-09603cddf507&redirect_uri=https%3A%2F%2Fc.eu16.visual.force.com%2Fapex%2FMercedes_API_Authorization&scope=mb:user:pool:reader%20mb:vehicle:status:general

 

On sait récupérer dans SF le code d’autorisation !

 

2.         Le code pour demander le jeton de session

 

Après avoir reçu le code d’autorisation, quand on travaillait à la main :

  • on devait récupérer les jetons de session
  • on pouvait récupérer les données avec l’API

Maintenant on doit faire la même chose qu’on faisait en Curl, mais en SF, avec le langage APEX.

Avec CuRL, à chaque fois :

  • on créait la requête (la commande tapée)
  • on l’exécutait (appel du serveur Mercedes par le serveur Salesforce), en lançant la commande
  • on traitait le résultat (en le lisant)

On va faire la même chose en programmant.

L’appel CuRL pour l’obtention de la session était

curl -X POST "https://api.secure.mercedes-benz.com/oidc10/auth/oauth/v2/token" \
-H "authorization: Basic ZGY4YTczYmUtYTY4Zi00.....hMWQwLWRjMmM0ZWI4MTU0ZA==" \
-H "content-type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=297ded07-43ea-4fcc-ab73-6b6376d787ca&redirect_uri=https%3A%2F%2Flocalhost.com"

Le Code correspondant en Apex est

  • Préparation de la requête
// creation d’une requête HTTP
HttpRequest req = new HttpRequest();
req.setMethod('POST');

// vers le serveur Mercedes
req.setEndpoint('https://api.secure.mercedes-benz.com/oidc10/auth/oauth/v2/token');

// avec le bon code d’auorisation
req.setHeader('authorization', 'Basic ZGY4YTczYmUtYTY4Zi0.....2NiZTMxYmUxMDM5OjBlYjRmMGE0LTM1YzgtNDc0Ny1hMWQwLWRjMmM0ZWI4MTU0ZA==');
req.setHeader('content-type', 'application/x-www-form-urlencoded');

// avec le message de demande de session
String messageBody =
'grant_type=authorization_code'
+'&code=297ded07-43ea-4fcc-ab73-6b6376d787ca'    +'&redirect_uri=https%3A%2F%2Fc.eu16.visual.force.com%2Fapex%2FMercedes_API_Authorization';
req.setBody(messageBody);
  • Exécution de la requête
//Execute de la requête HTTP
Http http = new Http();
HTTPResponse res = http.send(req);
  • Lecture de la réponse et traitement (juste un affichage)
String jsonResponse = res.getBody();

Map<String, Object> results = (Map<String, Object>)

JSON.deserializeUntyped(jsonResponse);

String access_token = (String)  results.get('access_token');
String token_type   = (String)  results.get('token_type');
Integer expires_in = (Integer) results.get('expires_in');
String refresh_token=  (String)  results.get('refresh_token');
String scope         = (String)  results.get('scope');

System.debug('access_token:' + access_token);
System.debug('token_type:' + token_type);
System.debug('expires_in:' + expires_in);
System.debug('refresh_token:' + refresh_token);
System.debug('scope:' + scope); 

Voici ce qu’on obtient en debug quand on lance le code dans la Console

3.         Le code pour faire un appel API : liste des voitures

De même, pour obtenir la liste des véhicules, on appelait avec CuRL

curl -X GET "https://api.mercedes-benz.com/experimental/connectedvehicle/v1/vehicles"  -H "accept: application/json"  -H "authorization: Bearer f9da01aa-0c07-412b-bb0c-f31a7b413b7b"

On fait les mêmes étapes en Apex :

  • Préparation de la requête (on passe le token obtenu comme avec CuRL)
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setHeader('authorization', 'Bearer '+access_token );
req.setHeader('accept', 'application/json');
req.setEndpoint('https://api.mercedes-benz.com/experimental/connectedvehicle/v1/vehicles');
  • Exécution de la requête
Http http = new Http();
HTTPResponse res = http.send(req);
  • Lecture de la réponse et traitement (ici on imprime juste la liste)
String jsonResponse = res.getBody();
System.debug('response:' + jsonResponse);

Voici ce qu’on obtient dans le Debug :

Et voici ! Reste à intégrer ce code manipulé à la main dans une application… Ce sera l’objet des travaux du prochain post !

A la découverte de salesforce (3/3) – Stocker des données sur le serveur Salesforce

Les objets que j’ai créés sont stockés dans la base de données de Salesforce. Les écrans de Salesforce vont les chercher pour les afficher, ou quand on modifie dans un écran il va dire à Salesforce de stocker le changement dans sa base de données.

Il y a un langage qui sert à aller chercher les données dans Salesforce : SOQL (Salesforce Object Query Language).

2.         Exemple liste des Cars : taper une requête dans la console

Pour essayer d’utiliser le langage de requête SOQL on peut quand on est connecté dans Salesforce aller dans le menu en haut à droite et lancer la Console.

Dans la nouvelle fenêtre qui s’ouvre (La Console), on peut aller dans le Query Editor, taper la requête suivante et cliquer sur Execute.

SELECT Id, Name, Car_Color__c, Car_number__c, Car_Model__c, Car_Owner__c FROM Car__c where car_color__c = 'red'

On obtient alors la liste des deux voitures rouges qui s’affiche.

3.         Exemple liste des Cars : dans le code de programmation coté serveur (Apex)

Dans la dernière partie, pour faire le composant visuel, on a parlé de la programmation en HTML et en Java Script dans le navigateur pour accéder à Salesforce.

Mais dans Salesforce il y a aussi un langage de programmation qui s’exécute sur le serveur de Salesforce. Ce langage s’appelle Apex.

Dans la partie suivante, je vais en avoir besoin pour programmer les communications avec le serveur Mercedes depuis Salesforce.

Ce langage Apex permet aussi d’appeler les requêtes vers la base de données qu’on a vue juste avant.

Pour le moment on va faire un petit essai d’exécution d’un petit programme Apex dans la console. Pour cela on va dans le menu Debug, on choisit Open Execute Anonymous Window, et on peut taper un petit programme Apex qui va lancer une requête.

Le code du petit programme Apex à saisir :

List<Car__c> listeDesVoituresRouges = [
SELECT Id, Name, Car_Color__c, Car_number__c
FROM Car__c
where car_color__c = 'red'
];

for (Car__c uneVoiture : listeDesVoituresRouges) {
System.debug('La voiture '+uneVoiture.Name+ ' est '+ uneVoiture.car_color__c);
}

On clique sur Execute

On peut aller regarder le résultat dans le log d’exécution (le fichier que SF a généré pendant que le programme s’exécute sur le serveur Salesforce)

Cette possibilité de programmation du côté des serveurs Salesforce sera utilisée beaucoup dans la partie suivante.

A la découverte de salesforce (2/3) – Développer des composants visuels

Dans Salesforce, une fois que l’on a configuré comme avant, on peut juste utiliser l’outil que Salesforce nous propose pour saisir des objets ou les voir ou les chercher.

Mais pour ce projet, je dois aller plus loin, et j’ai besoin de programmer des écrans spéciaux ou des calculs. C’est cette partie de programmation que j’avais déjà faite dans mes travaux personnels précédents (« Histoire des langages de programmation » et « Développer un jeu en Java »).

Dans cette partie du travail on va découvrir comment programmer dans Salesforce.

1.         Les outils

Pour pouvoir programmer dans Salesforce j’ai dû installer deux outils sur mon ordinateur.

a)              SFDX CLI (Command Line Interface)

Cet outil là nous sert à envoyer les programmes et les configurations réalisés vers Salesforce et de réceptionner ceux déjà réalisés.

Voici le guide pour l’installer :

https://developer.salesforce.com/docs/atlas.en-us.218.0.sfdx_setup.meta/sfdx_setup/sfdx_setup_install_cli.htm#sfdx_setup_install_cli

b)              Visual Studio

Pour travailler plus facilement j’ai installé un programme qui s’appelle Visual Studio. Dans ce programme on peut programmer facilement. C’est un outil comme Eclipse qu’on avait utilisé pour programmer en Java dans le Travail Personnel Précédent.

Voici le guide qui explique comment l’installer.

https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.install_setup_develop

Comme indiqué dans le guide d’installation, à cet outil on doit ajouter un pack d’extensions qui lui permet de savoir manipuler Salesforce et le CLI. : le Salesforce Extension Pack for Visual Studio Code.

Voilà, nous sommes prêts pour développer en Salesforce !

2.         Un premier test : Un panneau sur la voiture qui affiche sa couleur

Comme premier essai dans ce nouveau programme on va créer un petit panneau qui va afficher la couleur de la voiture.

a)              Créer un projet

Comme dans Eclipse il faut tout d’abord créer un projet dans Visual Studio où on va placer notre code.

Pour créer un projet, dans Visual Studio, on fait Ctrl shift P, on tape « SFDX :Create Project », on donne son nom « Cars » et l’endroit où stocker le projet sur le PC.

b)              Créer le composant

Une fois que le projet est prêt, pour créer le composant que l’on veut développer, on fait un clic droit sur le répertoire lwc du projet Cars, et on sélectionne « create lightning web component ». On lui donne son nom « Car Info ».

 

Visual studio a créé du coup trois fichiers pour le composant :

  • CarInfos.html : la page visible
  • CarInfos.js : le code du programme
  • CarInfos.js-meta.xml : des infos sur ce qu’on peut faire avec le composant

Pour coder ces trois fichiers, j’ai utilisé les exemples décrits dans le manuel du développeur Salesforce.

c)              Développer le composant Car Info

On dit qu’il est affichable dans la page d’une voiture dans CarInfos.js-meta.xml


<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="CarInfo">
<apiVersion>45.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Car__c</object>
</objects>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>

On code un contenu simple pour CarInfo.html : afficher la variable CarColor.


<template>
<lightning-card title="Car Information" icon-name="standard:people">
<div class="slds-m-around_medium">
<p>the car is {carColor}!</p>
</div>
</lightning-card>
</template>

On écrit le code javascript pour demander à SF la couleur dans CarInfos.js :

  • Au début du code, les lignes import {} cela dit que l’on utilise des possibilités de Salesforce
  • La ligne import CAR_COLOR_FIELD sert à dire qu’on va accéder aux voitures de la base de données SF
  • La ligne @api recordId sert à dire que le composant connaît la voiture qu’il doit afficher (cela va être passé par Salesforce au composant affiché).
  • La ligne @wire… sert à charger les informations de la voiture en mémoire et sa couleur…)
  • La fonction getCarColor est celle appelée par la page HTML pour obtenir la couleur.


import { LightningElement, api, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';

import CAR_COLOR_FIELD from '@salesforce/schema/Car__c.Car_Color__c';

const carFields = [CAR_COLOR_FIELD];

export default class CarInfo extends LightningElement {

@api recordId; // Car Id

@wire(getRecord, { recordId: '$recordId', fields: carFields })
car;

get carColor() {
    return getFieldValue(this.car.data, CAR_COLOR_FIELD);
}

}

d)              Envoyer le composant dans Salesforce

Avant d’envoyer le code vers Salesforce, on doit connecter le Visual Studio à Salesforce : Cela se fait dans le terminal du studio en tapant

sfdx force:auth:web:login -d -a mydevorg

Visual Studio ouvre une fenêtre qui demande de se connecter à Salesforce pour me connecter à mon environnement de développement.

 

Mon environnement Salesforce est alors relié à Visual Studio.

Maintenant, je peux envoyer le code du composant vers Salesforce en tapant :

sfdx force:source:deploy -p force-app -u thibault.leblond@wollef.org

e)              Installer le composant dans Salesforce sur la page voiture

La dernière étape est dans Salesforce d’éditer la page affichant la voiture pour ajouter le composant à la page.

On va sur une page qui affiche une voiture, en haut à droite on va dans le menu « Edit Page »

On arrive dans l’éditeur de page, le composant est disponible et peut être placé où l’on veut dans la page

f)               Utiliser le composant sur la page voiture

Une fois que l’on a sauvé la page, le composant s’affiche comme prévu : on a maintenant notre composant visuel en haut à droite !

 

 

 

A la découverte de Salesforce (1/3) – Créer son compte et Configurer

Pour pouvoir programmer le jeu j’avais besoin d’un serveur pour sauver des données et afficher des écrans et communiquer avec le serveur Mercedes.

On peut tout programmer à la main avec des outils élémentaires, sur son propre serveur (la base de données, le serveur pour les pages html,…), mais je n’avais pas assez de temps pour faire le jeu.

Donc j’ai cherché une solution plus rapide. Les grandes sociétés d’informatique proposent des outils faciles pour programmer dans le cloud.

L’une d’elles est Sales Force. Elle a été créée il y a 20 ans pour fournir des logiciels pour les vendeurs. Mais maintenant elle offre des possibilités pour programmer toutes sortes d’applications. Dans Salesforce on peut aussi gérer plein de données, accessibles en cloud avec des ordinateurs et des téléphones.

Le schéma suivant montre ce que l’on peut utiliser pour créer des applications avec Salesforce.

 

A.         Création et utilisation de l’environnement Salesforce

Pour pouvoir travailler sur Salesforce il fallait que je me crée un compte de développeur sur la page de connexion Salesforce.

https://developer.salesforce.com/signup

Quand on a terminé de faire le compte Salesforce on peut directement commencer à saisir des données dans Salesforce (par exemple ajouter des entreprises dans la base de données, et ajouter des personnes de contact dans les différentes entreprises).

On obtient par exemple le résultat suivant stocké dans Salesforce :

B.        Configuration des objets dans Salesforce

1.         Qu’est-ce que configurer ?

Non seulement Salesforce nous donne la possibilité d’utiliser des choses déjà configurées en standard (Les entreprises, les contacts que l’on vient d’utiliser) mais elle nous donne aussi la possibilité de créer des nouveaux objets.

Donc dans Salesforce on peut dans notre cas créer un Objet « voiture ».

2.         Créer un nouvel objet : voiture

On doit aller dans le menu « setup »

Là aller dans l’onglet « Object Manager »

C’est l’endroit où se trouvent toutes les configurations des objets de Salesforce.

En haut à droite cliquer sur « Create » puis « Custom Object », saisir le nom de l’objet (Car) et valider.

Pour accéder facilement aux voitures que l’on va maintenant pouvoir créer, on va aussi ajouter un onglet (tab) à Salesforce.

On va dans le « home » du set up, on cherche le menu « tab », en haut à droite on choisit « Create » puis « Custom Tab »

On indique à quoi doit ressembler le tab, puis on clique plusieurs fois sur Next pour valider.

On a maintenant un onglet (tab) qui permet de créer des voitures et d’y accéder, tout comme on créait des Entreprises (Account) et des Contacts.

3.         Ajouter la possibilité de saisir des informations pour une voiture

Si on ne faisait rien de plus on ne pourrait pas donner plus d’information sur une voiture. Pour pouvoir donner plus d’information sur une voiture (couleur, modèle, etc.), on devra dire à Salesforce de créer des champs dans l’objet Car pour donner plus de données.

Pour faire cela il faut aller dans l’Object Manager puis dans l’objet Car cliquer sur Fields and Relationship, ensuite cliquer sur new pour créer des nouveaux champs.

Pour chaque champ il faut dire le type (un nombre, une date, un texte, etc.), le nom du champ, les caractéristiques (longueur pour un texte, liste de valeurs possibles pour une liste de choix, etc.).

Par exemple pour le numéro d’immatriculation d’une voiture on indique la longueur maximale que l’on veut (ici 48 dans le print screen suivant).

On clique plusieurs fois sur Next dans les écrans puis on valide la création du champ en cliquant Save.

4.         Comment relier une voiture à un autre objet (par exemple le propriétaire)

Pour faire que la voiture a un propriétaire il faut créer un nouveau champ qui relie un contact avec la voiture au choix. C’est un champ de type « Lookup Relationship ». On doit dire vers quoi le champ dirige.

 

5.         Utiliser les nouveaux champs

Maintenant que les champs existent dans Salesforce, on peut compléter les voitures que l’on avait déjà créées :

Et voilà, maintenant la fiche de la voiture contient les nouvelles informations !

 

Cars and Clouds – Projet Personnel 2019

Pour mon travail personnel au Lycée Ermesinde pour l’année 2018-2019,    j’ai eu l’idée de regrouper des thèmes qui m’intéressent et que j’ai déjà examinés dans mes précédents travaux personnels :

A ces sujets que j’aime bien j’ajoute un nouveau thème : Les serveurs sur internet et le cloud computing. J’ai déjà exploré ce thème dans mon temps libre en créent un serveur Minecraft et en le personnalisant (j’en avais parlé dans ce blog).

Pour ce travail personnel je voulais faire un projet avec des voitures réelles qui échangent des données avec un serveur cloud sur lequel tourne un jeu que j’aurais programmé.

Pour la voiture j’ai décidé d’utiliser l’API de Mercedes (interface de programmation) qui permet d’échanger des informations comme la position, la vitesse, la consommation d’une voiture.

Pour le cloud, j’ai besoin d’un endroit pour stocker les informations, les afficher aux joueurs, communiquer et programmer les règles du jeu. Pour cela j’ai choisi les serveurs cloud de Salesforce.

Pour le jeu j’avais l’idée de créer une course virtuelle qui compare différents joueurs, ou plutôt des conducteurs sur le même trajet.

Ce projet a comporté différentes parties pratiques :

Cela a été un long travail mais c’est intéressant d’assembler des différents thèmes.

J’ai passé beaucoup de temps le Weekend et pendant les Vacances pour ce travail, une quinzaine de fois entre deux et quatre heures, car il y avait beaucoup plus de choses à découvrir que je ne le pensais quand j’ai commencé ! Mais cela a été très intéressant et je suis content de le partager avec vous dans ce blog.