Vous pouvez synchroniser vos demandes d'assistance entre Google Cloud et votre système de gestion de la relation client (CRM) (tel que Jira Service Desk, Zendesk et ServiceNow) en créant un connecteur pour les intégrer.
Ce connecteur utilise l'API Cloud Support (CSAPI) de Customer Care. Ce document explique comment créer un connecteur et l'utiliser. Vous pouvez ajuster la conception pour l'adapter à votre cas d'utilisation.
Hypothèses
Nous élaborons des hypothèses importantes concernant le fonctionnement de votre système CRM et la langue dans laquelle vous rédigez le connecteur. Si votre CRM offre des fonctionnalités différentes, vous pouvez tout de même créer un connecteur qui fonctionne parfaitement, mais vous devrez peut-être l'implémenter autrement que dans ce guide.
Ce guide est basé sur les hypothèses suivantes:
- Vous créez le connecteur avec Python et le microframework Flask.
- Nous supposons que vous utilisez Flask, car il s'agit d'un framework simple permettant de créer une petite application. Vous pouvez également utiliser d'autres langages ou frameworks, comme Java.
- Vous souhaitez synchroniser les pièces jointes, les commentaires, la priorité, les métadonnées de la demande et son état. Vous n'avez pas besoin de synchroniser toutes ces données, sauf si vous le souhaitez. Par exemple, ne synchronisez pas les pièces jointes si vous ne le souhaitez pas.
- Votre CRM présente des points de terminaison capables de lire et d'écrire les champs que vous souhaitez synchroniser. Si vous souhaitez synchroniser tous les champs comme nous le faisons dans ce guide, assurez-vous que les points de terminaison de votre CRM prennent en charge les opérations suivantes:
Opération Équivalent CSAPI Récupérez les demandes en utilisant un ID statique et immuable. cases.get
Créez des demandes. cases.create
Fermez les demandes. cases.close
Mettez à jour la priorité d'une demande. cases.patch
Lister les pièces jointes d'une demande cases.attachments.list
Télécharger une pièce jointe à une demande media.download
Importer une pièce jointe à une demande media.upload
Lister les commentaires sur une demande cases.comments.list
Ajouter un nouveau commentaire à une demande cases.comments.create
Parcourez les demandes*. cases.search
*Vous devez pouvoir filtrer les données en fonction de l'heure de leur dernière mise à jour. Il doit également y avoir un moyen de déterminer les demandes qui doivent être synchronisées avec le Customer Care Portfolio. Par exemple, si les demandes de votre CRM peuvent comporter des champs personnalisés, vous pouvez renseigner un champ booléen personnalisé nommé synchronizeWithGoogleCloudSupport
et filtrer les données en fonction de ces champs.
Conception de haut niveau
Le connecteur est entièrement créé à l'aide des produits Google Cloud et de votre CRM. Il s'agit d'une application App Engine qui exécute Python dans le microframework Flask. Elle utilise Cloud Tasks pour interroger régulièrement l'API CSAPI et votre CRM à la recherche de nouvelles demandes et de mises à jour de demandes existantes, puis elle synchronise les modifications entre elles. Certaines métadonnées concernant les demandes sont stockées dans Firestore, mais elles sont supprimées lorsqu'elles ne sont plus nécessaires.
Le schéma suivant illustre la conception générale:
Objectifs du connecteur
L'objectif principal du connecteur est que, lorsqu'une demande que vous souhaitez synchroniser est créée dans votre CRM, une demande correspondante est créée dans Customer Care et toutes les mises à jour ultérieures de la demande sont synchronisées entre les deux. De même, lorsqu'une demande est créée dans Customer Care, elle doit être synchronisée avec votre CRM.
Plus précisément, les aspects suivants des demandes doivent être synchronisés:
- Création de la demande :
- Lorsqu'une demande est créée dans un système, le connecteur doit créer une demande correspondante dans l'autre système.
- Si un système n'est pas disponible, la demande doit y être créée dès qu'il le sera.
- Commentaires :
- Lorsqu'un commentaire est ajouté à une demande dans un système, il doit être ajouté au dossier correspondant dans l'autre système.
- Pièces jointes :
- Lorsqu'une pièce jointe est ajoutée à une demande dans un système, elle doit être ajoutée à la demande correspondante dans l'autre système.
- Priorité :
- Lorsque la priorité d'une demande est mise à jour dans un système, la priorité du dossier correspondant dans l'autre système doit l'être également.
- État de la demande :
- Lorsqu'une demande est fermée dans un système, elle doit également l'être dans l'autre système.
Créer l'infrastructure
Produits Google Cloud
Le connecteur est une application App Engine qui stocke les données des demandes synchronisées à l'aide de Cloud Firestore configuré en mode Datastore. Elle utilise Cloud Tasks pour planifier des tâches à l'aide d'une logique de nouvelle tentative automatisée.
Pour accéder aux demandes dans Customer Care, le connecteur utilise un compte de service pour appeler l'API V2 Cloud Support. Vous devez accorder les autorisations appropriées au compte de service pour l'authentification.
Votre CRM
Le connecteur accède aux demandes dans votre CRM à l'aide d'un mécanisme que vous avez fourni. (probablement, en appelant une API exposée par votre CRM).
Considérations de sécurité pour votre organisation
Le connecteur synchronise les demandes de l'organisation dans laquelle vous le créez, ainsi que tous les projets enfants de l'organisation. Cela peut permettre aux utilisateurs de cette organisation d'accéder à des données de service client auxquelles vous ne voulez pas qu'ils aient accès. Réfléchissez bien à la façon dont vous structurez vos rôles IAM pour préserver la sécurité de votre organisation.
Conception détaillée
Configurer l'API CSAPI
Pour configurer l'API CSAPI, procédez comme suit:
- Souscrivez un service d'assistance Cloud Customer Care pour votre organisation.
- Activez l'API Cloud Support pour le projet dans lequel vous allez exécuter le connecteur.
- Récupérez les identifiants du compte de service Apps Framework par défaut que le connecteur utilisera .
- Attribuez les rôles suivants au compte de service au niveau de l'organisation :
Tech Support Editor
Organization Viewer
Pour en savoir plus sur la configuration de l'API CSAPI, consultez le guide de l'utilisateur de l'API Cloud Support V2.
Appeler CSAPI
Nous utilisons Python pour appeler CSAPI. Nous vous recommandons d'appeler CSAPI avec Python de deux façons:
- Bibliothèques clientes générées à partir de ses fichiers proto. Ils sont plus modernes et idiomatiques, mais ils ne permettent pas d'appeler les points de terminaison des pièces jointes de CSAPI. Pour en savoir plus, consultez la page GAPIC Generator.
- Bibliothèques clientes générées à partir de son document de découverte. Ces fichiers sont plus anciens, mais ils sont compatibles avec les pièces jointes. Pour en savoir plus, consultez la section Client API Google.
Voici un exemple d'appel de CSAPI à l'aide de bibliothèques clientes créées à partir de son document de découverte:
"""
Gets a support case using the Cloud Support API.
Before running, do the following:
- Set the GOOGLE_APPLICATION_CREDENTIALS environment variable to
your service account credentials.
- Install the Google API Python Client: https://github.com/googleapis/google-api-python-client
- Change NAME to point to a case that your service account has permission to get.
"""
import os
import googleapiclient.discovery
NAME = "projects/some-project/cases/43595344"
def main():
api_version = "v2"
supportApiService = googleapiclient.discovery.build(
serviceName="cloudsupport",
version=api_version,
discoveryServiceUrl=f"https://cloudsupport.googleapis.com/$discovery/rest?version={api_version}",
)
request = supportApiService.cases().get(
name=NAME,
)
print(request.execute())
if __name__ == "__main__":
main()
Pour d'autres exemples d'appel de l'API CSAPI, consultez ce dépôt.
Noms, identifiants et numéros des ressources Google
Utilisez organization_id
comme ID de votre organisation. Dans Customer Care, vous pouvez créer des demandes dans le cadre de votre organisation ou d'un projet au sein de votre organisation. Laissez project_id
le nom d'un projet dans lequel vous pourriez créer des cas.
Nom de la demande
Le nom d'une demande se présente comme suit:
organizations/{organization_id}/cases/{case_number}
projects/{project_id}/cases/{case_number}
Où case_number
est le numéro attribué à la demande. Par exemple, 51234456
.
Nom du commentaire
Le nom d'un commentaire se présente comme suit:
organizations/{organization_id}/cases/{case_number}/comments/{comment_id}
Où comment_id
est un numéro attribué à un commentaire. Par exemple, 3
. Par ailleurs, en plus des organisations, les projets sont autorisés comme parents.
Nom de la pièce jointe
Le nom d'une pièce jointe se présente comme suit:
organizations/{organization_id}/cases/{case_number}/attachments/{attachment_id}
Où attachment_id
est l'ID d'une pièce jointe dans une demande, le cas échéant. Par exemple, 0684M00000JvBpnQAF
. Par ailleurs, en plus des organisations, les projets sont autorisés comme parents.
Entités Firestore
CaseMapping
Un CaseMapping
est un objet défini pour stocker des métadonnées sur une demande.
Il est créé pour chaque demande synchronisée, puis supprimée lorsqu'elle n'est plus nécessaire. Pour en savoir plus sur les types de données acceptés dans Firebase, consultez Types de données acceptés.
Un CaseMapping
a les propriétés suivantes:
Propriété | Description | Type | Exemple |
---|---|---|---|
ID |
Clé primaire. Attribué automatiquement par Firestore lorsque CaseMapping est créé. |
Integer | 123456789 |
googleCaseName |
Nom complet de la demande, avec l'ID de l'organisation ou du projet et le numéro de demande | Chaîne de texte | organizations/123/cases/456 |
companyCaseID |
ID de la demande dans votre CRM. | Integer | 789 |
newContentAt |
Dernière fois que du nouveau contenu a été détecté dans le ticket Google ou dans votre CRM. | Date et heure | 0001-01-01T00:00:00Z |
resolvedAt |
L'horodatage de la résolution du problème. Utilisé pour supprimer les CaseMappings lorsqu'ils ne sont plus nécessaires. |
Date et heure | 0001-01-01T00:00:00Z |
companyUpdatesSyncedAt |
Horodatage de la dernière fois où le connecteur a interrogé votre CRM pour rechercher des mises à jour et synchronisé toute mise à jour avec la demande Google. Permet de détecter les pannes. | Date et heure | 0001-01-01T00:00:00Z |
googleUpdatesSyncedAt |
Horodatage de la dernière fois où le connecteur a interrogé Google pour rechercher des mises à jour et synchronisé toute mise à jour dans le ticket de votre CRM. Permet de détecter les pannes. | Date et heure | 0001-01-01T00:00:00Z |
outageCommentSentToGoogle |
Si une indisponibilité a été détectée, si un commentaire a été ajouté au dossier Google. Permet d'éviter l'ajout de plusieurs commentaires d'indisponibilité. | Booléen | False |
outageCommentSentToCompany |
Si une indisponibilité a été détectée, un commentaire a été ajouté au dossier de votre CRM. Permet d'éviter l'ajout de plusieurs commentaires d'indisponibilité. | Booléen | False |
priority |
Niveau de priorité de la demande. | Integer | 2 |
Global
Global
est un objet qui stocke des variables globales pour le connecteur.
Un seul objet Global
est créé et il n'est jamais supprimé. Elle se présente comme suit :
Propriété | Description | Type | Exemple |
---|---|---|---|
ID | Clé primaire. Attribué automatiquement par Firestore lors de la création de cet objet. | Integer | 123456789 |
google_last_polled_at |
Heure à laquelle le service Customer Care a été interrogé pour la dernière fois pour obtenir des informations. | Date et heure | 0001-01-01T00:00:00Z |
company_last_polled_at |
Heure à laquelle votre entreprise a été interrogée pour des mises à jour pour la dernière fois. | Date et heure | 0001-01-01T00:00:00Z |
Tasks
PollGoogleForUpdates
This task is scheduled to run every 60 seconds. It does the following:
- Search for recently updated cases:
- Call
CSAPI.SearchCases(organization_id, page_size=100, filter="update_time>{Global.google_last_polled_at - GOOGLE_POLLING_WINDOW}")
- Continue fetching pages as long as a
nextPageToken
is returned.
GOOGLE_POLLING_WINDOW
represents the period during which a case is continually checked for updates, even after it has been synced. The larger its value, the more tolerant the connector is to changes that are added while a case is syncing. We recommend that you set GOOGLE_POLLING_WINDOW
to 30 minutes to avoid any problems with comments being added out of order.
- Make a
CaseMapping
for any new cases:
- If
CaseMapping
does not exist for case.name
and case.create_time
is less than 30 days ago, then create a CaseMapping
with the following values:
Property
Value
caseMappingID
N/A
googleCaseName
case.name
companyCaseID
null
newContentAt
current_time
resolvedAt
null
companyUpdatesSyncedAt
current_time
googleUpdatesSyncedAt
null
outageCommentSentToGoogle
False
outageCommentSentToCompany
False
priority
case.priority
(converted to an integer)
- Queue tasks to sync all recently updated cases:
- Specifically,
SyncGoogleCaseToCompany(case.name)
.
- Update
CaseMappings
:
- For each open
CaseMapping
not recently updated, update CaseMapping.googleUpdatesSyncedAt
to the current time.
- Update last polled time:
- Update
Global.google_last_polled_at
in Firestore to the current time.
Retry logic
Configure this task to retry a few times within the first minute and then expire.
PollCompanyForUpdates
This task is scheduled to run every 60 seconds. It does the following:
- Search for recently updated cases:
- Call
YOUR_CRM.SearchCases(page_size=100, filter=”update_time>{Global.company_last_polled_at - COMPANY_POLLING_WINDOW} AND synchronizeWithGoogleCloudSupport=true”)
.
COMPANY_POLLING_WINDOW
can be set to whatever time duration works for you. For example, 5 minutes.
- Make a
CaseMapping
for any new cases:
- For each case, if
CaseMapping
does not exist for case.id
and case.create_time
is less than 30 days ago, create a CaseMapping
that looks like this:
Property
Value
caseMappingID
N/A
googleCaseName
null
companyCaseID
case.id
newContentAt
current_time
resolvedAt
null
companyUpdatesSyncedAt
null
googleUpdatesSyncedAt
current_time
outageCommentSentToGoogle
False
outageCommentSentToCompany
False
priority
case.priority
(converted to an integer)
- Queue tasks to sync all recently updated cases:
- Specifically, queue
SyncCompanyCaseToGoogle(case.name)
.
- Update
CaseMappings
:
- For each open
CaseMapping
not recently updated, update CaseMapping.companyUpdatesSyncedAt
to the current time.
- Update last polled time:
- Update
Global.company_last_polled_at
in Firestore to the current time.
Retry logic
Configure this task to retry a few times within the first minute and then expire.
SyncGoogleUpdatesToCompany(case_name)
Implementation
- Get the case and case mapping:
- Get
CaseMapping
for case_name
.
- Call
CSAPI.GetCase(case_name)
.
- If necessary, update resolved time and case status:
- If
CaseMapping.resolvedAt == null
and case.status == CLOSED
:
- Set
CaseMapping.resolvedAt
to case.update_time
- Close the case in the CRM as well
- Try to connect to an existing case in the CRM. If unable, then make a new one:
- If
CaseMapping.companyCaseID == null
:
- Try to get your CRM case with
custom_field_google_name == case_name
custom_field_google_name
is a custom field you create on the case object in your CRM.
- If the CRM case can't be found, call
YOUR_CRM.CreateCase(case)
with the following case:
Case field name in your CRM
Value
Summary
Case.diplay_name
Priority
Case.priority
Description
"CONTENT MIRRORED FROM GOOGLE SUPPORT:\n" + Case.description
Components
"Google Cloud
"
Customer Ticket (custom_field_google_name
)
case_name
Attachments
N/A
- Update the
CaseMapping
with a CaseMapping
that looks like this:
Property
Value
companyCaseID
new_case.id
googleUpdatesSyncedAt
current_time
- Add comment to Google case: "This case is now syncing with Company Case:
{case_id}
".
- Synchronize the comments:
- Get all comments:
- Call
CSAPI.ListComments(case_name, page_size=100)
. The maximum page size is 100. Continue retrieving successive pages until the oldest comment retrieved is older than googleUpdatesSyncedAt - GOOGLE_POLLING_WINDOW
.
- Call
YOUR_CRM.GetComments(case_id, page_size=50)
. Continue retrieving successive pages until the oldest comment retrieved is older than companyUpdatesSyncedAt - COMPANY_POLLING_WINDOW
.
- Optional: If you'd like, consider caching comments in some way so you can avoid making extra calls here. We leave the implementation of that up to you.
- Compare both lists of comments to determine if there are new comments on the Google Case.
- For each new Google comment:
- Call
YOUR_CRM.AddComment(comment.body)
, starting with "[Google Comment {comment_id}
by {comment.actor.display_name}
]".
- Repeat for attachments.
- Update
CaseMapping.googleUpdatesSyncedAt
to the current time.
Retry logic
Configure this task to retry indefinitely with exponential backoff.
SyncCompanyUpdatesToGoogle(case_id)
Implementation:
- Get the case and case mapping.
- Get
CaseMapping
for case.id
.
- Call
YOUR_CRM.GetCase(case.id)
.
- If necessary, update resolved time and case status:
- If
CaseMapping.resolvedAt == null
and case.status == CLOSED
:
- Set
CaseMapping.resolvedAt
to case.update_time
- Close the case in CSAPI as well
- Try to connect to an existing case in CSAPI. If unable, then make a new one:
- If
CaseMapping.googleCaseName == null
:
- Search through cases in CSAPI. Try to find a case that has a comment containing “This case is now syncing with Company Case:
{case_id}
”. If you're able to find one, then set googleCaseName
equal to its name.
- Otherwise, call
CSAPI.CreateCase(case)
:
- Synchronize the comments.
- Get all comments for the case from CSAPI and the CRM:
- Call
CSAPI.ListComments(case_name, page_size=100)
. Continue retrieving successive pages until the oldest comment retrieved is older than googleUpdatesSyncedAt - GOOGLE_POLLING_WINDOW
.
- Call
YOUR_CRM.GetComments(case_id, page_size=50)
. Continue retrieving successive pages until the oldest comment retrieved is older than companyUpdatesSyncedAt - COMPANY_POLLING_WINDOW
.
- NOTE: If you'd like, consider caching comments in some way so you can avoid making extra calls here. We leave the implementation of that up to you.
- Compare both lists of comments to determine if there are new comments on the CRM case.
- For each new Company comment:
- Call
CSAPI.AddComment
, starting with "[Company Comment {comment.id}
by {comment.author.displayName}
]".
- Repeat for attachments.
- Update
CaseMapping.companyUpdatesSyncedAt
to the current time.
Retry logic
Configure this task to retry indefinitely with exponential backoff.
CleanUpCaseMappings
This task is scheduled to run daily. It deletes any CaseMapping
for a case that has been closed for 30 days according to resolvedAt
.
Retry logic
Configure this task to retry with exponential backoff for up to 24 hours.
DetectOutages
This task is scheduled to run once every 5 minutes. It detects outages and alerts your Google and CRM cases (when possible) if a case is not syncing within the expected latency_tolerance
.
latency_tolerance
is defined as follows, where Time Since New Content = currentTime - newContentAt
:
Priority
Fresh (<1 hour)
Default (1 hour-1day)
Stale (>1 day)
P0
10 min
10 min
15 min
P1
10 min
15 min
60 min
P2
10 min
20 min
120 min
P3
10 min
20 min
240 min
P4
10 min
30 min
240 min
The latency that is relevant for the connector is not request latency, but rather the latency between when a change is made in one system and when it is propagated to the other. We make latency_tolerance
dependent on priority and freshness to avoid spamming cases unnecessarily. If there is a short outage, such as scheduled maintenance on either system, we don't need to alert P4
cases that haven't been updated recently.
When DetectOutages
runs, it does the following:
- Determine if a
CaseMapping
needs an outage comment, whereupon it adds one:
- For each
CaseMapping
in Firestore:
- If recently added (
companyCaseId
or googleUpdatesSyncedAt
is not defined), then ignore.
- If
current_time > googleUpdatesSyncedAt + latency_tolerance OR current_time > companyUpdatesSyncedAt + latency_tolerance
:
- If
!outageCommentSentToGoogle
:
- Try:
- Add comment to Google that "This case has not synced properly in
{duration since sync}
."
- Set
outageCommentSentToGoogle = True
.
- If
!outageCommentSentToCompany
:
- Try:
- Add comment to your CRM that "This case has not synced properly in
{duration since sync}
."
- Set
outageCommentSentToCompany = True
.
- Else:
- If
outageCommentSentToGoogle
:
- Try:
- Add comment to Google that "Syncing has resumed."
- Set
outageCommentSentToGoogle = False
.
- If
outageCommentSentToCompany
:
- Try:
- Add comment to your CRM that "Syncing has resumed."
- Set
outageCommentSentToCompany = False
.
- Return a failing status code (4xx or 5xx) if an outage is detected. This causes any monitoring you've set up to notice that there is a problem with the task.
Retry logic
Configure this task to retry a few times within the first 5 minutes and then expire.
What's next
Your connector is now ready to use.
If you'd like, you can also implement unit tests and integration tests. Also, you can add monitoring to check that the connector is working correctly on an ongoing basis.