Sincronizza le richieste di assistenza clienti Google Cloud

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 API (CSAPI) del cliente. Questo documento fornisce un esempio di come creare e utilizzare un connettore. Puoi perfezionare il design per adattarlo al tuo caso d'uso.

Ipotesi

Facciamo alcune ipotesi importanti sul funzionamento del tuo CRM e sul linguaggio in cui scrivi il connettore. Se il tuo sistema CRM ha funzionalità diverse, puoi comunque creare un connettore che funzioni perfettamente, ma potrebbe essere necessario implementarlo in un modo diverso da quello descritto in questa guida.

Questa guida si basa sui seguenti presupposti:

  • Stai creando il connettore con Python e il microframework Flask.
    • Presumiamo che tu stia utilizzando 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 questi dati, a meno che tu non voglia farlo. Ad esempio, non sincronizzare gli allegati se non vuoi farlo.
  • Il sistema CRM espone endpoint in grado di leggere e scrivere i campi che vuoi sincronizzare. Se vuoi sincronizzare tutti i campi come quelli descritti in questa guida, assicurati che gli endpoint del tuo sistema CRM supportano le seguenti operazioni:
    Operazione Equivalente CSAPI
    Ricevi richieste utilizzando alcuni ID statici e invariati. 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
    Scaricare un allegato di una richiesta. media.download
    Caricare un allegato in una richiesta. media.upload
    Elenca i commenti su una richiesta. cases.comments.list
    Aggiungi un nuovo commento su una richiesta. cases.comments.create
    Ricerca tra le custodie*. cases.search

*Devi poter filtrare in base all'ora dell'ultimo aggiornamento. Deve inoltre essere possibile identificare i casi da sincronizzare con l'assistenza clienti. Ad esempio, se i casi nel tuo CRM possono avere campi personalizzati, puoi compilare un campo booleano personalizzato denominato synchronizeWithGoogleCloudSupport e filtrarlo in base a questo.

Progettazione di alto livello

Il connettore è stato realizzato interamente utilizzando i prodotti Google Cloud e il tuo sistema CRM. Si tratta di un'app App Engine che esegue Python nel microframework Flask. Utilizza Cloud Tasks per eseguire periodicamente il polling di CSAPI e del tuo CRM in caso di nuovi casi e aggiornamenti di casi esistenti e sincronizza le modifiche tra loro. Alcuni metadati relativi alle richieste sono archiviati in Firestore, ma vengono eliminati dopo che non sono più necessari.

Il seguente diagramma mostra il design generale:

Il connettore chiama CSAPI e il sistema CRM. Comprende un'app App Engine, Cloud Tasks e alcuni dati in Firestore.

Obiettivi connettore

L'obiettivo principale del connettore è che quando una richiesta da sincronizzare viene creata nel sistema CRM, viene creata una richiesta corrispondente nell'assistenza clienti e tutti gli aggiornamenti successivi alla richiesta vengono sincronizzati tra loro. Allo stesso modo, quando una richiesta viene creata nell'assistenza clienti, deve essere sincronizzata con il sistema CRM.

Nello specifico, è necessario sincronizzare i seguenti aspetti dei casi:

  • Creazione della richiesta:
    • Quando viene creata una richiesta in un sistema, il connettore deve creare una richiesta corrispondente nell'altro sistema.
    • Se un sistema non è disponibile, è necessario creare la richiesta al suo interno una volta che sarà disponibile.
  • Commenti:
    • Quando un commento viene aggiunto a una richiesta in un sistema, deve essere aggiunto alla richiesta corrispondente nell'altro sistema.
  • Allegati:
    • Quando si aggiunge un allegato a una richiesta in un sistema, è necessario aggiungerlo 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 è chiusa in un sistema, deve essere chiusa anche nell'altro sistema.

Infrastruttura

Prodotti Google Cloud

Il connettore è un'app App Engine che utilizza Cloud Firestore configurato nella modalità Datastore per archiviare i dati sulle richieste sincronizzate. Utilizza Cloud Tasks per pianificare i job con la logica dei nuovi tentativi automatici.

Per accedere alle richieste nell'assistenza clienti, il connettore utilizza un account di servizio per chiamare l'API V2 Cloud Support. Devi concedere all'account di servizio le autorizzazioni appropriate per l'autenticazione.

Il tuo CRM

Il connettore accede alle richieste nel tuo sistema CRM utilizzando un meccanismo fornito da te. Presumibilmente, chiamando un'API esposta dal sistema CRM.

Considerazioni sulla sicurezza per l'organizzazione

Il connettore sincronizza le richieste nell'organizzazione in cui è stato creato e in tutti i progetti secondari dell'organizzazione. In questo modo, gli utenti dell'organizzazione potrebbero accedere a 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:

  1. Acquista un servizio di assistenza clienti Google Cloud per la tua organizzazione.
  2. Per il progetto in cui eseguirai il connettore, abilita l'API Cloud Support.
  3. Recupera le credenziali dell'account di servizio Apps Framework predefinito che deve essere utilizzato dal connettore .
  4. Concedi i seguenti ruoli all'account di servizio a livello di organizzazione:
    • Tech Support Editor
    • Organization Viewer

Per saperne di più sulla configurazione di CSAPI, consulta la guida dell'utente dell'API Cloud Support V2.

Chiama CSAPI

Utilizziamo Python per chiamare CSAPI. Consigliamo i seguenti due modi per chiamare CSAPI con Python:

  1. Librerie client generate dai relativi protocollo. Sono più moderne e idiomatiche, ma non supportano la chiamata agli endpoint di collegamento di CSAPI. Per scoprire di più, consulta GAPIC Generator.
  2. Librerie client generate dal relativo documento di rilevamento. Non sono più recenti, ma supportano gli allegati. Per ulteriori informazioni, consulta Client API di Google.

Ecco un esempio di chiamata CSAPI che utilizza librerie client create nel suo 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 chiamata CSAPI, consulta questo repository.

Nomi, ID e numeri delle risorse di Google

Consenti a organization_id di essere l'ID della tua organizzazione. Puoi creare richieste all'interno dell'assistenza clienti della tua organizzazione o di un progetto al suo interno. project_id è il nome di un progetto in cui potresti creare delle richieste.

Nome della richiesta

Il nome di una richiesta di assistenza è 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 del commento

Il nome di un commento ha il seguente aspetto:

  • 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, sono ammessi anche i genitori.

Nome allegato

Il nome di un allegato è simile al seguente:

  • organizations/{organization_id}/cases/{case_number}/attachments/{attachment_id}

Dove attachment_id è l'ID di un allegato, se presente. Ad esempio, 0684M00000JvBpnQAF. Inoltre, oltre alle organizzazioni, sono ammessi anche i genitori.

Entità Firestore

CaseMapping

Un CaseMapping è un oggetto definito per archiviare i metadati su una richiesta.

Viene creato per ogni richiesta che viene sincronizzata ed eliminata quando non è più necessaria. Per ulteriori informazioni 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 al momento della 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 Google o nella richiesta nel tuo sistema CRM. Data e ora 0001-01-01T00:00:00Z
resolvedAt Un timestamp con la data in cui è stata risolta la richiesta di assistenza Google. Utilizzati per eliminare CaseMappings quando non sono più necessari. Data e ora 0001-01-01T00:00:00Z
companyUpdatesSyncedAt Un timestamp dell'ultima volta in cui il connettore ha eseguito il polling del tuo CRM per aggiornamenti e ha sincronizzato eventuali aggiornamenti alla richiesta Google. Utilizzato per il rilevamento di interruzioni. Data e ora 0001-01-01T00:00:00Z
googleUpdatesSyncedAt Un timestamp dell'ultima volta in cui il connettore ha eseguito il polling con Google degli aggiornamenti e della sincronizzazione di eventuali aggiornamenti sulla richiesta del CRM. Utilizzato per il rilevamento di interruzioni. Data e ora 0001-01-01T00:00:00Z
outageCommentSentToGoogle Se è stata rilevata un'interruzione, indica se è stato aggiunto un commento alla richiesta Google. Utilizzato per impedire l'aggiunta di più commenti sull'interruzione. Booleano False
outageCommentSentToCompany Se è stata rilevata un'interruzione, indica se è stato aggiunto un commento alla richiesta del tuo CRM. Utilizzato per impedire l'aggiunta di più commenti sull'interruzione. Booleano False
priority Il livello di priorità della richiesta. Numero intero 2

Global

Global è un oggetto che memorizza le variabili globali per il connettore.

Viene creato un solo oggetto Global che non viene mai eliminato. Ecco il risultato:

Proprietà Descrizione Tipo Esempio
IDChiave primaria. Assegnato automaticamente da Firestore al momento della creazione dell'oggetto. Numero intero 123456789
google_last_polled_at La data e l'ora dell'ultimo controllo da parte dell'assistenza clienti degli aggiornamenti. Data e ora 0001-01-01T00:00:00Z
company_last_polled_at Il momento dell'ultimo sondaggio sugli 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.