Menyinkronkan kasus Layanan Pelanggan Google Cloud

Anda dapat menyinkronkan kasus dukungan antara Google Cloud dan sistem Pengelolaan Hubungan Pelanggan (CRM) (seperti Jira Service Desk, Zendesk, dan ServiceNow) dengan membuat konektor untuk mengintegrasikannya.

Konektor ini menggunakan Cloud Support API (CSAPI) Layanan Pelanggan. Dokumen ini memberikan contoh cara membuat konektor dan menggunakannya. Anda dapat menyesuaikan desain agar sesuai dengan kasus penggunaan Anda.

Asumsi

Kami membuat beberapa asumsi penting tentang cara kerja CRM Anda dan bahasa yang Anda gunakan untuk memasukkan konektor. Jika CRM Anda memiliki kemampuan yang berbeda, Anda masih dapat membuat konektor yang berfungsi dengan baik - tetapi Anda mungkin perlu menerapkannya dengan cara yang berbeda dari yang kami lakukan dalam panduan ini.

Panduan ini didasarkan pada asumsi berikut:

  • Anda membuat konektor dengan Python dan mikroframework Flask.
    • Kami berasumsi bahwa Anda menggunakan Flask karena ini adalah framework yang mudah untuk membuat aplikasi kecil. Anda juga dapat menggunakan bahasa atau framework lain, seperti Java.
  • Anda ingin menyinkronkan lampiran, komentar, prioritas, metadata kasus, dan status kasus. Anda tidak perlu menyinkronkan semua data tersebut kecuali jika menginginkannya. Misalnya, jangan menyinkronkan lampiran jika Anda tidak menginginkannya.
  • CRM Anda mengekspos endpoint yang mampu membaca dan menulis kolom yang ingin Anda sinkronkan. Jika Anda ingin menyinkronkan setiap kolom seperti yang kami lakukan di panduan ini, pastikan endpoint CRM Anda mendukung operasi berikut:
    Operasi Setara dengan CSAPI
    Mendapatkan kasus menggunakan beberapa ID statis yang tidak berubah. cases.get
    Membuat kasus. cases.create
    Menutup kasus. cases.close
    Memperbarui prioritas kasus. cases.patch
    Mencantumkan lampiran pada kasus. cases.attachments.list
    Download lampiran pada kasus. media.download
    Upload lampiran ke kasus. media.upload
    Mencantumkan komentar pada kasus. cases.comments.list
    Menambahkan komentar baru pada kasus. cases.comments.create
    Telusuri kasus*. cases.search

*Anda harus dapat memfilter menurut waktu pembaruan terakhir. Harus ada juga beberapa cara untuk menentukan kasus mana yang ingin disinkronkan ke Layanan Pelanggan. Misalnya, jika kasus di CRM dapat memiliki kolom kustom, Anda dapat mengisi kolom boolean kustom bernama synchronizeWithGoogleCloudSupport dan memfilter berdasarkan kolom tersebut.

Desain tingkat tinggi

Konektor dibuat sepenuhnya menggunakan produk Google Cloud dan CRM Anda. Ini adalah aplikasi App Engine yang menjalankan Python di mikroframework Flask. Layanan ini menggunakan Cloud Tasks untuk secara berkala melakukan polling CSAPI dan CRM Anda untuk menemukan kasus baru dan memperbarui kasus yang ada, serta menyinkronkan perubahan di antara kasus tersebut. Beberapa metadata tentang kasus disimpan di Firestore, tetapi dihapus setelah tidak diperlukan lagi.

Diagram berikut menunjukkan desain tingkat tinggi:

Konektor memanggil CSAPI dan CRM Anda. Library ini terdiri dari Aplikasi App Engine, Cloud Tasks, dan beberapa data di Firestore.

Sasaran konektor

Sasaran utama konektor adalah saat kasus yang ingin disinkronkan dibuat di CRM, kasus yang sesuai dibuat di Layanan Pelanggan, dan semua pembaruan kasus berikutnya akan disinkronkan di antara keduanya. Demikian pula, saat dibuat di Layanan Pelanggan, kasus tersebut harus disinkronkan ke CRM Anda.

Secara khusus, aspek kasus berikut harus disinkronkan:

  • Pembuatan kasus:
    • Jika casing dibuat di satu sistem, konektor harus membuat kasus yang sesuai di sistem lain.
    • Jika satu sistem tidak tersedia, kasus harus dibuat di dalamnya setelah tersedia.
  • Komentar:
    • Jika komentar ditambahkan ke kasus di satu sistem, komentar tersebut harus ditambahkan ke kasus yang sesuai di sistem lainnya.
  • Lampiran:
    • Saat lampiran ditambahkan ke kasus di satu sistem, lampiran harus ditambahkan ke kasus yang sesuai di sistem lain.
  • Prioritas:
    • Jika prioritas kasus di satu sistem diupdate, prioritas kasus yang sesuai di sistem lain harus diupdate.
  • Status kasus:
    • Jika kasus ditutup di satu sistem, kasus tersebut juga harus ditutup di sistem lain.

Infrastruktur

Produk Google Cloud

Konektornya adalah aplikasi App Engine yang menggunakan Cloud Firestore yang dikonfigurasi dalam mode Datastore untuk menyimpan data tentang kasus yang disinkronkan. Cloud Tasks menggunakan Cloud Tasks untuk menjadwalkan tugas dengan logika percobaan ulang otomatis.

Untuk mengakses kasus di Layanan Pelanggan, konektor menggunakan akun layanan untuk memanggil V2 Cloud Support API. Anda harus memberikan izin yang sesuai ke akun layanan untuk autentikasi.

CRM Anda

Konektor mengakses kasus di CRM Anda menggunakan mekanisme yang Anda sediakan. Mungkin, dengan memanggil API yang diekspos oleh CRM Anda.

Pertimbangan keamanan untuk organisasi Anda

Konektor menyinkronkan kasus-kasus di organisasi tempat Anda membuatnya dan semua project turunan organisasi. Tindakan ini mungkin mengizinkan pengguna di organisasi tersebut mengakses data dukungan pelanggan yang mungkin tidak Anda izinkan untuk diakses oleh mereka. Pertimbangkan dengan cermat cara Anda menyusun peran IAM untuk menjaga keamanan dalam organisasi.

Desain mendetail

Menyiapkan CSAPI

Untuk menyiapkan CSAPI, ikuti langkah-langkah berikut:

  1. Beli layanan dukungan Layanan Pelanggan Cloud untuk organisasi Anda.
  2. Untuk project yang akan menjalankan konektor, aktifkan Cloud Support API.
  3. Dapatkan kredensial akun layanan Apps Framework default yang akan digunakan oleh konektor .
  4. Berikan peran berikut ke akun layanan di tingkat organisasi:
    • Tech Support Editor
    • Organization Viewer

Untuk mengetahui informasi selengkapnya tentang menyiapkan CSAPI, lihat Panduan pengguna Cloud Support API V2.

Panggil CSAPI

Kita menggunakan Python untuk memanggil CSAPI. Kami merekomendasikan dua cara berikut untuk memanggil CSAPI dengan Python:

  1. Library klien yang dihasilkan dari proto-nya. Ini lebih modern dan idiomatis, tetapi tidak mendukung pemanggilan endpoint lampiran CSAPI. Lihat Generator GAPIC untuk mempelajari lebih lanjut.
  2. Library klien yang dihasilkan dari dokumen discovery-nya. File ini memang lebih tua, tetapi mendukung lampiran. Lihat Klien Google API untuk mempelajari lebih lanjut.

Berikut adalah contoh pemanggilan CSAPI menggunakan library klien yang dibuat dari dokumen penemuannya:

"""
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()

Untuk contoh lain tentang cara memanggil CSAPI, lihat repositori ini.

Nama, ID, dan Nomor Resource Google

Biarkan organization_id menjadi ID organisasi Anda. Anda dapat membuat kasus di Layanan Pelanggan di bawah organisasi atau di project dalam organisasi Anda. Biarkan project_id menjadi nama project tempat Anda dapat membuat kasus.

Nama Kasus

Nama kasus akan terlihat seperti berikut:

  • organizations/{organization_id}/cases/{case_number}
  • projects/{project_id}/cases/{case_number}

Dengan case_number adalah nomor yang ditetapkan ke kasus. Contoh, 51234456.

Nama Komentar

Nama komentar akan terlihat seperti berikut:

  • organizations/{organization_id}/cases/{case_number}/comments/{comment_id}

Dengan comment_id adalah nomor yang ditetapkan untuk komentar. Contoh, 3. Selain itu, selain organisasi, proyek juga diperbolehkan bagi orang tua.

Nama Lampiran

Nama lampiran terlihat seperti berikut:

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

Dengan attachment_id adalah ID lampiran dalam kasus, jika ada. Contoh, 0684M00000JvBpnQAF. Selain itu, selain organisasi, proyek juga diperbolehkan bagi orang tua.

Entitas Firestore

CaseMapping

CaseMapping adalah objek yang ditentukan untuk menyimpan metadata tentang kasus.

File ini dibuat untuk setiap kasus yang disinkronkan dan dihapus saat tidak diperlukan lagi. Untuk mempelajari lebih lanjut jenis data yang didukung di Firebase, lihat Jenis data yang didukung.

CaseMapping memiliki properti berikut:

Properti Deskripsi Jenis Contoh
ID Kunci utama. Otomatis ditetapkan oleh Firestore saat CaseMapping dibuat. Bilangan bulat 123456789
googleCaseName Nama lengkap kasus, dengan organisasi atau ID project dan nomor kasus. String teks organizations/123/cases/456
companyCaseID ID kasus di CRM Anda. Bilangan bulat 789
newContentAt Terakhir kali konten baru terdeteksi di kasus Google atau kasus di CRM Anda. Tanggal dan waktu 0001-01-01T00:00:00Z
resolvedAt Stempel waktu saat kasus Google diselesaikan. Digunakan untuk menghapus CaseMappings saat tidak lagi diperlukan. Tanggal dan waktu 0001-01-01T00:00:00Z
companyUpdatesSyncedAt Stempel waktu saat terakhir kali konektor berhasil melakukan polling pada CRM Anda untuk mengetahui pembaruan dan menyinkronkan pembaruan ke kasus Google. Digunakan untuk mendeteksi pemadaman layanan. Tanggal dan waktu 0001-01-01T00:00:00Z
googleUpdatesSyncedAt Stempel waktu saat terakhir kali konektor berhasil melakukan polling di Google untuk mendapatkan info terbaru dan menyinkronkan pembaruan apa pun ke kasus CRM Anda. Digunakan untuk mendeteksi pemadaman layanan. Tanggal dan waktu 0001-01-01T00:00:00Z
outageCommentSentToGoogle Jika gangguan terdeteksi, apakah komentar telah ditambahkan ke kasus Google atau tidak. Digunakan untuk mencegah beberapa komentar pemadaman ditambahkan. Boolean False
outageCommentSentToCompany Jika pemadaman layanan terdeteksi, apakah komentar telah ditambahkan ke kasus CRM Anda. Digunakan untuk mencegah beberapa komentar pemadaman ditambahkan. Boolean False
priority Tingkat prioritas kasus. Bilangan bulat 2

Global

Global adalah objek yang menyimpan variabel global untuk konektor.

Hanya satu objek Global yang dibuat dan tidak pernah dihapus. Tampilannya seperti ini:

Properti Deskripsi Jenis Contoh
IDKunci utama. Otomatis ditetapkan oleh Firestore saat objek ini dibuat. Bilangan bulat 123456789
google_last_polled_at Waktu terakhir kali Layanan Pelanggan disurvei untuk pembaruan. Tanggal dan waktu 0001-01-01T00:00:00Z
company_last_polled_at Waktu terakhir kali perusahaan Anda melakukan polling untuk mendapatkan pembaruan. Tanggal dan waktu 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.