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:
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:
- Beli layanan dukungan Layanan Pelanggan Cloud untuk organisasi Anda.
- Untuk project yang akan menjalankan konektor, aktifkan Cloud Support API.
- Dapatkan kredensial akun layanan Apps Framework default yang akan digunakan oleh konektor .
- 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:
- 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.
- 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 |
---|---|---|---|
ID | Kunci 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.