Puoi sincronizzare le tue richieste di assistenza tra Google Cloud e il tuo sistema di gestione dei rapporti con i clienti (CRM) (ad esempio Jira Service Desk, Zendesk e ServiceNow) creando un connettore per l'integrazione.
Questo connettore utilizza l'API Cloud Support (CSAPI) dell'assistenza clienti. Questo documento fornisce un esempio di come creare un connettore e utilizzarlo. Puoi modificare il design per adattarlo al tuo caso d'uso.
Ipotesi
Facciamo alcune ipotesi importanti sul funzionamento del tuo sistema CRM e sulla lingua in cui scrivi il connettore. Se il tuo sistema CRM ha funzionalità diverse, puoi comunque creare un connettore che funzioni perfettamente, ma potresti doverlo implementare in un modo diverso da quello descritto in questa guida.
Questa guida si basa sui seguenti presupposti:
- Stai creando il connettore con il microframework Python e Flask.
- Partiamo dal presupposto che tu stia usando Flask perché è un framework semplice in cui creare una piccola app. Puoi anche usare altri linguaggi o framework, come Java.
- Vuoi sincronizzare allegati, commenti, priorità, metadati della richiesta e stato della richiesta. Non è necessario sincronizzare tutti i dati, a meno che tu non voglia farlo. Ad esempio, non sincronizzare gli allegati se non vuoi farlo.
- Il CRM espone gli endpoint in grado di leggere e scrivere i campi che vuoi sincronizzare. Se vuoi sincronizzare ogni campo come in questa guida, assicurati che gli endpoint del tuo CRM supportino le seguenti operazioni:
Operazione Equivalente CSAPI Ricevi le richieste utilizzando un ID statico che non cambia. cases.get
Crea richieste. cases.create
Chiudi le richieste. cases.close
Aggiorna la priorità di una richiesta. cases.patch
Elenca gli allegati di una richiesta. cases.attachments.list
Scarica l'allegato di una richiesta. media.download
Carica un allegato a una richiesta. media.upload
Elenca i commenti su una richiesta. cases.comments.list
Aggiungere un nuovo commento a una richiesta. cases.comments.create
Cerca tra le richieste*. cases.search
*Devi poter filtrare in base all'ora dell'ultimo aggiornamento. Deve esistere anche un modo per stabilire quali richieste devono essere sincronizzate con l'assistenza clienti. Ad esempio, se le richieste nel tuo sistema CRM possono avere campi personalizzati, puoi compilare un campo booleano personalizzato denominato synchronizeWithGoogleCloudSupport
e applicare un filtro in base a questi dati.
Design di alto livello
Il connettore è creato interamente utilizzando i prodotti Google Cloud e il tuo CRM. È un'app App Engine che esegue Python nel microframework Flask. Utilizza Cloud Tasks per eseguire periodicamente il polling di CSAPI e del tuo CRM alla ricerca di nuove richieste e aggiornamenti a quelle esistenti e sincronizza le modifiche tra le due. Alcuni metadati relativi alle richieste vengono archiviati in Firestore, ma vengono eliminati quando non sono più necessari.
Il seguente diagramma mostra il design di alto livello:
Obiettivi del connettore
L'obiettivo principale del connettore è che, quando nel tuo CRM viene creata una richiesta da sincronizzare, nell'assistenza clienti viene creata una richiesta corrispondente e tutti i successivi aggiornamenti della richiesta vengono sincronizzati tra i due. Allo stesso modo, quando viene creata una richiesta nell'assistenza clienti, deve essere sincronizzata con il tuo CRM.
Nello specifico, è necessario sincronizzare i seguenti aspetti delle richieste:
- Creazione della richiesta:
- Quando viene creata una richiesta in un sistema, il connettore deve crearne una corrispondente nell'altro sistema.
- Se un sistema non è disponibile, la richiesta deve essere creata al suo interno quando è disponibile.
- Commenti:
- Quando un commento viene aggiunto a una richiesta in un sistema, deve essere aggiunto alla richiesta corrispondente nell'altro sistema.
- Allegati:
- Quando un allegato viene aggiunto a una richiesta in un sistema, deve essere aggiunto alla richiesta corrispondente nell'altro sistema.
- Priorità:
- Quando viene aggiornata la priorità di una richiesta in un sistema, è necessario aggiornare la priorità della richiesta corrispondente nell'altro sistema.
- Stato della richiesta:
- Quando una richiesta viene chiusa in un sistema, deve essere chiusa anche nell'altro.
Infrastruttura
Prodotti Google Cloud
Il connettore è un'app App Engine che utilizza Cloud Firestore configurato in modalità Datastore per archiviare i dati relativi alle richieste sincronizzate. Utilizza Cloud Tasks per pianificare i job con una logica di nuovo tentativo automatizzata.
Per accedere alle richieste nell'assistenza clienti, il connettore utilizza un account di servizio per chiamare l'API Cloud Support V2. Per l'autenticazione devi concedere le autorizzazioni appropriate all'account di servizio.
Il tuo CRM
Il connettore accede alle richieste nel tuo sistema CRM utilizzando un meccanismo fornito da te. Possibilmente, chiamando un'API esposta dal tuo CRM.
Considerazioni sulla sicurezza per la tua organizzazione
Il connettore sincronizza le richieste nell'organizzazione in cui lo crei e in tutti i progetti figlio dell'organizzazione. Questo potrebbe consentire agli utenti di quell'organizzazione di accedere ai dati dell'assistenza clienti a cui non vuoi che accedano. Valuta attentamente come strutturare i ruoli IAM per mantenere la sicurezza nella tua organizzazione.
Progettazione dettagliata
Configura CSAPI
Per configurare CSAPI, segui questi passaggi:
- Acquista un servizio di assistenza clienti Google Cloud per la tua organizzazione.
- Per il progetto in cui eseguirai il connettore, abilita l'API Cloud Support.
- Recupera le credenziali dell'account di servizio Apps Framework predefinito che deve essere utilizzato dal connettore .
- Concedi i seguenti ruoli all'account di servizio a livello di organizzazione:
Tech Support Editor
Organization Viewer
Per ulteriori informazioni sulla configurazione di CSAPI, consulta la guida dell'utente su Cloud Support API V2.
Chiama CSAPI
Utilizziamo Python per chiamare CSAPI. Consigliamo i due modi seguenti per chiamare CSAPI con Python:
- Librerie client generate dai relativi proto. Queste sono più moderne e idiomatiche, ma non supportano la chiamata agli endpoint di collegamento di CSAPI. Per ulteriori informazioni, consulta GAPIC Generator.
- Librerie client generate dal relativo documento di rilevamento. Queste versioni sono meno recenti, ma supportano gli allegati. Per ulteriori informazioni, consulta Client API di Google.
Ecco un esempio di chiamata CSAPI utilizzando le librerie client ricavate dal relativo documento di rilevamento:
"""
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()
Per altri esempi di chiamate a CSAPI, consulta questo repository.
Nomi, ID e numeri delle risorse Google
Consenti a organization_id
di essere l'ID della tua organizzazione. Puoi creare richieste nell'assistenza clienti all'interno della tua organizzazione o in un progetto all'interno della tua organizzazione. Consenti a project_id
di essere il nome di un progetto in cui potresti creare richieste.
Nome richiesta
Il nome di una richiesta è simile al seguente:
organizations/{organization_id}/cases/{case_number}
projects/{project_id}/cases/{case_number}
dove case_number
è il numero assegnato alla richiesta. Ad esempio, 51234456
.
Nome commento
Il nome di un commento è simile al seguente:
organizations/{organization_id}/cases/{case_number}/comments/{comment_id}
Dove comment_id
è un numero assegnato a un commento. Ad esempio, 3
. Inoltre, oltre alle organizzazioni, anche i progetti sono consentiti ai genitori.
Nome allegato
Il nome di un allegato è simile al seguente:
organizations/{organization_id}/cases/{case_number}/attachments/{attachment_id}
Dove attachment_id
rappresenta l'ID di un allegato in una richiesta, se presente. Ad esempio, 0684M00000JvBpnQAF
. Inoltre, oltre alle organizzazioni, anche i progetti sono consentiti ai genitori.
Entità Firestore
CaseMapping
Un CaseMapping
è un oggetto definito per archiviare i metadati relativi a una richiesta.
Viene creata per ogni richiesta sincronizzata ed eliminata quando non è più necessaria. Per scoprire di più sui tipi di dati supportati in Firebase, consulta Tipi di dati supportati.
Un CaseMapping
ha le seguenti proprietà:
Proprietà | Descrizione | Tipo | Esempio |
---|---|---|---|
ID |
Chiave primaria. Assegnato automaticamente da Firestore alla creazione di CaseMapping . |
Numero intero | 123456789 |
googleCaseName |
Il nome completo della richiesta, con l'ID organizzazione o progetto e il numero della richiesta. | Stringa di testo | organizations/123/cases/456 |
companyCaseID |
L'ID della richiesta nel tuo sistema CRM. | Numero intero | 789 |
newContentAt |
L'ultima volta che sono stati rilevati nuovi contenuti nella richiesta di assistenza Google o nella richiesta nel tuo CRM. | Data e ora | 0001-01-01T00:00:00Z |
resolvedAt |
Un timestamp che indica quando è stata risolta la richiesta di assistenza Google. Utilizzata per eliminare CaseMappings quando non sono più necessari. |
Data e ora | 0001-01-01T00:00:00Z |
companyUpdatesSyncedAt |
Un timestamp relativo all'ultima volta che il connettore ha eseguito correttamente il polling del CRM per verificare la presenza di aggiornamenti e ha sincronizzato gli eventuali aggiornamenti con la richiesta di assistenza Google. Utilizzato per rilevare le interruzioni. | Data e ora | 0001-01-01T00:00:00Z |
googleUpdatesSyncedAt |
Un timestamp relativo all'ultima volta che il connettore ha eseguito correttamente il polling di Google per gli aggiornamenti e ha sincronizzato gli eventuali aggiornamenti della richiesta del CRM. Utilizzato per rilevare le interruzioni. | Data e ora | 0001-01-01T00:00:00Z |
outageCommentSentToGoogle |
Nel caso in cui sia stata rilevata un'interruzione, se è stato aggiunto un commento alla richiesta di assistenza Google. Utilizzata per impedire l'aggiunta di più commenti dell'interruzione. | Booleano | False |
outageCommentSentToCompany |
Nel caso in cui sia stata rilevata un'interruzione, se è stato aggiunto un commento alla richiesta del tuo CRM. Utilizzata per impedire l'aggiunta di più commenti dell'interruzione. | Booleano | False |
priority |
Il livello di priorità della richiesta. | Numero intero | 2 |
Global
Global
è un oggetto che archivia le variabili globali per il connettore.
Viene creato un solo oggetto Global
che non viene mai eliminato. Ha il seguente aspetto:
Proprietà | Descrizione | Tipo | Esempio |
---|---|---|---|
ID | Chiave primaria. Assegnato automaticamente da Firestore quando viene creato questo oggetto. | Numero intero | 123456789 |
google_last_polled_at |
L'ora dell'ultima richiesta di aggiornamenti dell'assistenza clienti. | Data e ora | 0001-01-01T00:00:00Z |
company_last_polled_at |
L'ora dell'ultimo sondaggio per gli aggiornamenti della tua azienda. | Data e ora | 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.