1. Giới thiệu
Các ứng dụng Google Chat đưa các dịch vụ và tài nguyên của bạn vào Google Chat, cho phép người dùng nhận thông tin và thực hiện hành động nhanh chóng mà không cần rời khỏi cuộc trò chuyện.
Trong lớp học lập trình này, bạn sẽ tìm hiểu cách tạo và triển khai một ứng dụng bỏ phiếu bằng Node.js và Cloud Functions.
Kiến thức bạn sẽ học được
- Sử dụng Cloud Shell
- Triển khai lên Cloud Functions
- Nhận thông tin đầu vào của người dùng bằng lệnh dấu gạch chéo và hộp thoại
- Tạo thẻ tương tác
2. Thiết lập và yêu cầu
Tạo một dự án trên Google Cloud, sau đó bật các API và dịch vụ mà ứng dụng Chat sẽ sử dụng
Điều kiện tiên quyết
Để phát triển một ứng dụng Google Chat, bạn cần có tài khoản Google Workspace có quyền truy cập vào Google Chat. Nếu bạn chưa có tài khoản Google Workspace, hãy tạo một tài khoản rồi đăng nhập trước khi tiếp tục với lớp học lập trình này.
Thiết lập môi trường theo tốc độ của riêng bạn
- Mở Google Cloud Console rồi tạo một dự án.
Hãy nhớ mã dự án, một tên riêng biệt cho tất cả dự án trên Google Cloud (tên ở trên đã được sử dụng và sẽ không hoạt động đối với bạn, xin lỗi!). Sau này trong lớp học lập trình này, chúng ta sẽ gọi nó làPROJECT_ID
.
- Tiếp theo, để sử dụng các tài nguyên của Google Cloud, hãy bật tính năng thanh toán trong Cloud Console.
Việc thực hiện lớp học lập trình này sẽ không tốn nhiều chi phí, nếu có. Hãy nhớ làm theo mọi hướng dẫn trong phần "Dọn dẹp" ở cuối lớp học lập trình để biết cách tắt các tài nguyên nhằm tránh bị tính phí ngoài phạm vi hướng dẫn này. Người dùng mới của Google Cloud đủ điều kiện tham gia chương trình Dùng thử miễn phí 300 USD.
Google Cloud Shell
Mặc dù có thể vận hành Google Cloud từ xa trên máy tính xách tay, nhưng trong lớp học lập trình này, chúng ta sẽ sử dụng Google Cloud Shell, một môi trường dòng lệnh chạy trong Google Cloud.
Kích hoạt Cloud Shell
- Trong Cloud Console, hãy nhấp vào Kích hoạt Cloud Shell
.
Lần đầu tiên mở Cloud Shell, bạn sẽ thấy một thông báo chào mừng mô tả. Nếu bạn thấy thông báo chào mừng, hãy nhấp vào Tiếp tục. Thông báo chào mừng sẽ không xuất hiện lại. Sau đây là thông báo chào mừng:
Quá trình cấp phép và kết nối với Cloud Shell chỉ mất vài giây. Sau khi kết nối, bạn sẽ thấy Cloud Shell Terminal:
Máy ảo này được tải sẵn tất cả các công cụ phát triển mà bạn cần. Nền tảng này cung cấp một thư mục chính có dung lượng 5 GB và chạy trên Google Cloud, giúp tăng cường đáng kể hiệu suất mạng và hoạt động xác thực. Bạn có thể thực hiện tất cả các thao tác trong lớp học lập trình này bằng trình duyệt hoặc Chromebook.Sau khi kết nối với Cloud Shell, bạn sẽ thấy rằng mình đã được xác thực và dự án đã được đặt thành mã dự án của bạn. - Chạy lệnh sau trong Cloud Shell để xác nhận rằng bạn đã được xác thực:
Nếu bạn được nhắc uỷ quyền cho Cloud Shell thực hiện lệnh gọi API GCP, hãy nhấp vào Uỷ quyền.gcloud auth list
Đầu ra của lệnh Nếu tài khoản của bạn không được chọn theo mặc định, hãy chạy:Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com>
$ gcloud config set account <ACCOUNT>
- Xác nhận rằng bạn đã chọn đúng dự án. Trong Cloud Shell, hãy chạy:
Đầu ra của lệnhgcloud config list project
Nếu dự án chính xác không được trả về, bạn có thể đặt dự án đó bằng lệnh sau:[core] project = <PROJECT_ID>
Đầu ra của lệnhgcloud config set project <PROJECT_ID>
Updated property [core/project].
Khi hoàn tất lớp học lập trình này, bạn sẽ sử dụng các thao tác trên dòng lệnh và chỉnh sửa tệp. Để chỉnh sửa tệp, bạn có thể sử dụng trình chỉnh sửa mã tích hợp của Cloud Shell (Cloud Shell Editor) bằng cách nhấp vào Open Editor (Mở trình chỉnh sửa) ở phía bên phải của thanh công cụ Cloud Shell. Các trình chỉnh sửa phổ biến như Vim và Emacs cũng có trong Cloud Shell.
3. Bật API Cloud Functions, Cloud Build và Google Chat
Trong Cloud Shell, hãy bật các API và dịch vụ sau:
gcloud services enable \ cloudfunctions \ cloudbuild.googleapis.com \ chat.googleapis.com
Thao tác này có thể mất vài phút để hoàn tất.
Sau khi hoàn tất, một thông báo thành công tương tự như thông báo này sẽ xuất hiện:
Operation "operations/acf.cc11852d-40af-47ad-9d59-477a12847c9e" finished successfully.
4. Tạo ứng dụng Chat ban đầu
Khởi chạy dự án
Để bắt đầu, bạn sẽ tạo và triển khai một ứng dụng đơn giản "Xin chào thế giới". Ứng dụng trò chuyện là các dịch vụ web phản hồi các yêu cầu https và phản hồi bằng một tải trọng JSON. Đối với ứng dụng này, bạn sẽ dùng Node.js và Cloud Functions.
Trong Cloud Shell, hãy tạo một thư mục mới có tên là poll-app
rồi chuyển đến thư mục đó:
mkdir ~/poll-app cd ~/poll-app
Tất cả công việc còn lại cho lớp học lập trình và các tệp bạn sẽ tạo đều nằm trong thư mục này.
Khởi động dự án Node.js:
npm init
NPM sẽ hỏi một số câu hỏi về cấu hình dự án, chẳng hạn như tên và phiên bản. Đối với mỗi câu hỏi, hãy nhấn ENTER
để chấp nhận các giá trị mặc định. Điểm truy cập mặc định là một tệp có tên index.js
. Chúng ta sẽ tạo tệp này ở bước tiếp theo.
Tạo phần phụ trợ của ứng dụng Chat
Đã đến lúc bắt đầu tạo ứng dụng. Hãy tạo một tệp có tên index.js
với nội dung sau:
/**
* App entry point.
*/
exports.app = async (req, res) => {
if (!(req.method === 'POST' && req.body)) {
res.status(400).send('')
}
const event = req.body;
let reply = {};
if (event.type === 'MESSAGE') {
reply = {
text: `Hello ${event.user.displayName}`
};
}
res.json(reply)
}
Ứng dụng này chưa làm được nhiều, nhưng không sao cả. Bạn sẽ thêm nhiều chức năng hơn sau.
Triển khai ứng dụng
Để triển khai ứng dụng "Hello world", bạn sẽ triển khai Cloud Functions, định cấu hình ứng dụng Chat trong Google Cloud Console và gửi một tin nhắn kiểm thử đến ứng dụng để xác minh việc triển khai.
Triển khai Cloud Function
Để triển khai Cloud Functions của ứng dụng "Hello world", hãy nhập lệnh sau:
gcloud functions deploy app --trigger-http --security-level=secure-always --allow-unauthenticated --runtime nodejs14
Khi hoàn tất, đầu ra sẽ có dạng như sau:
availableMemoryMb: 256
buildId: 993b2ca9-2719-40af-86e4-42c8e4563a4b
buildName: projects/595241540133/locations/us-central1/builds/993b2ca9-2719-40af-86e4-42c8e4563a4b
entryPoint: app
httpsTrigger:
securityLevel: SECURE_ALWAYS
url: https://us-central1-poll-app-codelab.cloudfunctions.net/app
ingressSettings: ALLOW_ALL
labels:
deployment-tool: cli-gcloud
name: projects/poll-app-codelab/locations/us-central1/functions/app
runtime: nodejs14
serviceAccountEmail: poll-app-codelab@appspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/gcf-upload-us-central1-66a01777-67f0-46d7-a941-079c24414822/94057943-2b7c-4b4c-9a21-bb3acffc84c6.zip
status: ACTIVE
timeout: 60s
updateTime: '2021-09-17T19:30:33.694Z'
versionId: '1'
Ghi lại URL của hàm đã triển khai trong thuộc tính httpsTrigger.url
. Bạn sẽ sử dụng chỉ mục này trong bước tiếp theo.
Định cấu hình ứng dụng
Để định cấu hình ứng dụng, hãy chuyển đến trang Cấu hình cuộc trò chuyện trong Cloud Console
- Trong phần Tên ứng dụng, hãy nhập "PollCodelab".
- Trong URL hình đại diện, hãy nhập
https://raw.githubusercontent.com/google/material-design-icons/master/png/social/poll/materialicons/24dp/2x/baseline_poll_black_24dp.png
. - Trong phần Mô tả, hãy nhập "Ứng dụng bỏ phiếu cho lớp học lập trình".
- Trong phần Chức năng, hãy chọn Nhận tin nhắn riêng tư và Tham gia không gian và cuộc trò chuyện nhóm.
- Trong phần Connection settings (Chế độ cài đặt kết nối), hãy chọn HTTP endpoint URL (URL điểm cuối HTTP) rồi dán URL cho Cloud Function (thuộc tính
httpsTrigger.url
trong phần cuối cùng). - Trong mục Quyền, hãy chọn Một số người và nhóm cụ thể trong miền của bạn rồi nhập địa chỉ email của bạn.
- Nhấp vào lưu.
Giờ đây, ứng dụng đã sẵn sàng để gửi tin nhắn.
Kiểm thử ứng dụng
Trước khi tiếp tục, hãy kiểm tra xem ứng dụng có hoạt động hay không bằng cách thêm ứng dụng đó vào một không gian trong Google Chat.
- Truy cập vào Google Chat.
- Bên cạnh phần Trò chuyện, hãy nhấp vào + > Tìm ứng dụng.
- Nhập "PollCodelab" vào thanh tìm kiếm.
- Nhấp vào Chat.
- Để nhắn tin cho ứng dụng, hãy nhập "Xin chào" rồi nhấn Enter.
Ứng dụng sẽ phản hồi bằng một thông báo chào ngắn gọn.
Giờ đây, khi đã có một cấu trúc cơ bản, đã đến lúc bạn biến cấu trúc đó thành một thứ hữu ích hơn!
5. Xây dựng các tính năng thăm dò ý kiến
Tổng quan nhanh về cách ứng dụng sẽ hoạt động
Ứng dụng này có 2 phần chính:
- Một lệnh dấu gạch chéo hiển thị hộp thoại để định cấu hình cuộc thăm dò.
- Một thẻ tương tác để bỏ phiếu và xem kết quả.
Ứng dụng cũng cần một số trạng thái để lưu trữ cấu hình và kết quả của cuộc thăm dò. Bạn có thể thực hiện việc này bằng Firestore hoặc bất kỳ cơ sở dữ liệu nào, hoặc trạng thái có thể được lưu trữ trong chính các thông báo của ứng dụng. Vì ứng dụng này dùng để tạo các cuộc thăm dò ý kiến nhanh và không chính thức của một nhóm, nên việc lưu trữ trạng thái trong các thông báo của ứng dụng sẽ rất phù hợp với trường hợp sử dụng này.
Mô hình dữ liệu cho ứng dụng (được biểu thị bằng Typescript) là:
interface Poll {
/* Question/topic of poll */
topic: string;
/** User that submitted the poll */
author: {
/** Unique resource name of user */
name: string;
/** Display name */
displayName: string;
};
/** Available choices to present to users */
choices: string[];
/** Map of user ids to the index of their selected choice */
votes: { [key: string]: number };
}
Ngoài chủ đề hoặc câu hỏi và danh sách lựa chọn, trạng thái này còn bao gồm mã nhận dạng và tên của tác giả cũng như các phiếu bầu đã ghi lại. Để ngăn người dùng bỏ phiếu nhiều lần, phiếu bầu được lưu trữ dưới dạng bản đồ mã nhận dạng người dùng cho chỉ mục của lựa chọn mà họ đã chọn.
Tất nhiên, có nhiều cách tiếp cận khác nhau, nhưng đây là điểm khởi đầu tốt để chạy các cuộc thăm dò ý kiến nhanh trong một không gian.
Triển khai lệnh cấu hình thăm dò
Để cho phép người dùng bắt đầu và định cấu hình cuộc thăm dò ý kiến, hãy thiết lập một lệnh gạch chéo mở một hộp thoại. Đây là một quy trình gồm nhiều bước:
- Đăng ký lệnh dấu gạch chéo để bắt đầu một cuộc thăm dò ý kiến.
- Tạo hộp thoại thiết lập cuộc thăm dò ý kiến.
- Cho phép ứng dụng nhận dạng và xử lý lệnh dấu gạch chéo.
- Tạo thẻ tương tác để giúp người dùng bỏ phiếu trong cuộc thăm dò ý kiến.
- Triển khai mã cho phép ứng dụng chạy các cuộc thăm dò ý kiến.
- Triển khai lại hàm trên đám mây.
Đăng ký lệnh dấu gạch chéo
Để đăng ký một lệnh gạch chéo, hãy quay lại trang Cấu hình Chat trong bảng điều khiển (API và dịch vụ > Trang tổng quan > Hangouts Chat API > Cấu hình).
- Trong mục Lệnh dấu gạch chéo, hãy nhấp vào Thêm lệnh dấu gạch chéo mới.
- Trong phần Tên, hãy nhập "/poll"
- Trong Command id, hãy nhập "1"
- Trong phần Mô tả, hãy nhập "Tạo một cuộc thăm dò ý kiến".
- Chọn Mở hộp thoại.
- Nhấp vào Xong.
- Nhấp vào Lưu.
Giờ đây, ứng dụng sẽ nhận ra lệnh /poll
và mở một hộp thoại. Tiếp theo, hãy định cấu hình hộp thoại.
Tạo biểu mẫu cấu hình dưới dạng hộp thoại
Lệnh dấu gạch chéo này sẽ mở một hộp thoại để bạn định cấu hình chủ đề và các lựa chọn có thể có của cuộc thăm dò. Tạo một tệp mới có tên là config-form.js
với nội dung sau:
/** Upper bounds on number of choices to present. */
const MAX_NUM_OF_OPTIONS = 5;
/**
* Build widget with instructions on how to use form.
*
* @returns {object} card widget
*/
function helpText() {
return {
textParagraph: {
text: 'Enter the poll topic and up to 5 choices in the poll. Blank options will be omitted.',
},
};
}
/**
* Build the text input for a choice.
*
* @param {number} index - Index to identify the choice
* @param {string|undefined} value - Initial value to render (optional)
* @returns {object} card widget
*/
function optionInput(index, value) {
return {
textInput: {
label: `Option ${index + 1}`,
type: 'SINGLE_LINE',
name: `option${index}`,
value: value || '',
},
};
}
/**
* Build the text input for the poll topic.
*
* @param {string|undefined} topic - Initial value to render (optional)
* @returns {object} card widget
*/
function topicInput(topic) {
return {
textInput: {
label: 'Topic',
type: 'MULTIPLE_LINE',
name: 'topic',
value: topic || '',
},
};
}
/**
* Build the buttons/actions for the form.
*
* @returns {object} card widget
*/
function buttons() {
return {
buttonList: {
buttons: [
{
text: 'Submit',
onClick: {
action: {
function: 'start_poll',
},
},
},
],
},
};
}
/**
* Build the configuration form.
*
* @param {object} options - Initial state to render with form
* @param {string|undefined} options.topic - Topic of poll (optional)
* @param {string[]|undefined} options.choices - Text of choices to display to users (optional)
* @returns {object} card
*/
function buildConfigurationForm(options) {
const widgets = [];
widgets.push(helpText());
widgets.push(topicInput(options.topic));
for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
const choice = options?.choices?.[i];
widgets.push(optionInput(i, choice));
}
widgets.push(buttons());
// Assemble the card
return {
sections: [
{
widgets,
},
],
};
}
exports.MAX_NUM_OF_OPTIONS = MAX_NUM_OF_OPTIONS;
exports.buildConfigurationForm = buildConfigurationForm;
Mã này tạo biểu mẫu hộp thoại cho phép người dùng thiết lập cuộc thăm dò ý kiến. Thư viện này cũng xuất một hằng số cho số lượng lựa chọn tối đa mà một câu hỏi có thể có. Bạn nên tách biệt việc tạo mã đánh dấu giao diện người dùng thành các hàm không trạng thái với mọi trạng thái được truyền dưới dạng tham số. Điều này giúp bạn dễ dàng sử dụng lại, và sau đó thẻ này sẽ được hiển thị trong nhiều ngữ cảnh.
Việc triển khai này cũng phân tách thẻ thành các đơn vị hoặc thành phần nhỏ hơn. Mặc dù không bắt buộc, nhưng kỹ thuật này là một phương pháp hay vì có xu hướng dễ đọc và dễ duy trì hơn khi xây dựng các giao diện phức tạp.
Để xem mẫu JSON hoàn chỉnh mà công cụ này tạo, hãy xem trong công cụ Card Builder.
Xử lý lệnh dấu gạch chéo
Lệnh gạch chéo xuất hiện dưới dạng các sự kiện MESSAGE
khi được gửi đến ứng dụng. Hãy cập nhật index.js
để kiểm tra sự hiện diện của lệnh gạch chéo thông qua sự kiện MESSAGE
và phản hồi bằng một hộp thoại. Thay thế index.js
bằng nội dung sau:
const { buildConfigurationForm, MAX_NUM_OF_OPTIONS } = require('./config-form');
/**
* App entry point.
*/
exports.app = async (req, res) => {
if (!(req.method === 'POST' && req.body)) {
res.status(400).send('')
}
const event = req.body;
let reply = {};
// Dispatch slash and action events
if (event.type === 'MESSAGE') {
const message = event.message;
if (message.slashCommand?.commandId === '1') {
reply = showConfigurationForm(event);
}
} else if (event.type === 'CARD_CLICKED') {
if (event.action?.actionMethodName === 'start_poll') {
reply = await startPoll(event);
}
}
res.json(reply);
}
/**
* Handles the slash command to display the config form.
*
* @param {object} event - chat event
* @returns {object} Response to send back to Chat
*/
function showConfigurationForm(event) {
// Seed the topic with any text after the slash command
const topic = event.message?.argumentText?.trim();
const dialog = buildConfigurationForm({
topic,
choices: [],
});
return {
actionResponse: {
type: 'DIALOG',
dialogAction: {
dialog: {
body: dialog,
},
},
},
};
}
/**
* Handle the custom start_poll action.
*
* @param {object} event - chat event
* @returns {object} Response to send back to Chat
*/
function startPoll(event) {
// Not fully implemented yet -- just close the dialog
return {
actionResponse: {
type: 'DIALOG',
dialogAction: {
actionStatus: {
statusCode: 'OK',
userFacingMessage: 'Poll started.',
},
},
},
}
}
Giờ đây, ứng dụng sẽ hiển thị một hộp thoại khi lệnh /poll
được gọi. Kiểm thử hoạt động tương tác bằng cách triển khai lại Cloud Function từ Cloud Shell.
gcloud functions deploy app --trigger-http --security-level=secure-always
Sau khi Cloud Function triển khai, hãy gửi thông báo cho ứng dụng bằng lệnh /poll
để kiểm thử lệnh dấu gạch chéo và hộp thoại. Hộp thoại này sẽ gửi một sự kiện CARD_CLICKED
cùng với thao tác tuỳ chỉnh start_poll
. Sự kiện này được xử lý trong điểm truy cập đã cập nhật, nơi sự kiện gọi phương thức startPoll
. Hiện tại, phương thức startPoll
được tạo stub để chỉ đóng hộp thoại. Trong phần tiếp theo, bạn sẽ triển khai chức năng bỏ phiếu và kết nối tất cả các phần với nhau.
Triển khai thẻ biểu quyết
Để triển khai phần bỏ phiếu của ứng dụng, hãy bắt đầu bằng cách xác định thẻ tương tác cung cấp giao diện để mọi người bỏ phiếu.
Triển khai giao diện bình chọn
Tạo một tệp có tên là vote-card.js
với nội dung sau:
/**
* Creates a small progress bar to show percent of votes for an option. Since
* width is limited, the percentage is scaled to 20 steps (5% increments).
*
* @param {number} voteCount - Number of votes for this option
* @param {number} totalVotes - Total votes cast in the poll
* @returns {string} Text snippet with bar and vote totals
*/
function progressBarText(voteCount, totalVotes) {
if (voteCount === 0 || totalVotes === 0) {
return '';
}
// For progress bar, calculate share of votes and scale it
const percentage = (voteCount * 100) / totalVotes;
const progress = Math.round((percentage / 100) * 20);
return '▀'.repeat(progress);
}
/**
* Builds a line in the card for a single choice, including
* the current totals and voting action.
*
* @param {number} index - Index to identify the choice
* @param {string|undefined} value - Text of the choice
* @param {number} voteCount - Current number of votes cast for this item
* @param {number} totalVotes - Total votes cast in poll
* @param {string} state - Serialized state to send in events
* @returns {object} card widget
*/
function choice(index, text, voteCount, totalVotes, state) {
const progressBar = progressBarText(voteCount, totalVotes);
return {
keyValue: {
bottomLabel: `${progressBar} ${voteCount}`,
content: text,
button: {
textButton: {
text: 'vote',
onClick: {
action: {
actionMethodName: 'vote',
parameters: [
{
key: 'state',
value: state,
},
{
key: 'index',
value: index.toString(10),
},
],
},
},
},
},
},
};
}
/**
* Builds the card header including the question and author details.
*
* @param {string} topic - Topic of the poll
* @param {string} author - Display name of user that created the poll
* @returns {object} card widget
*/
function header(topic, author) {
return {
title: topic,
subtitle: `Posted by ${author}`,
imageUrl:
'https://raw.githubusercontent.com/google/material-design-icons/master/png/social/poll/materialicons/24dp/2x/baseline_poll_black_24dp.png',
imageStyle: 'AVATAR',
};
}
/**
* Builds the configuration form.
*
* @param {object} poll - Current state of poll
* @param {object} poll.author - User that submitted the poll
* @param {string} poll.topic - Topic of poll
* @param {string[]} poll.choices - Text of choices to display to users
* @param {object} poll.votes - Map of cast votes keyed by user ids
* @returns {object} card
*/
function buildVoteCard(poll) {
const widgets = [];
const state = JSON.stringify(poll);
const totalVotes = Object.keys(poll.votes).length;
for (let i = 0; i < poll.choices.length; ++i) {
// Count votes for this choice
const votes = Object.values(poll.votes).reduce((sum, vote) => {
if (vote === i) {
return sum + 1;
}
return sum;
}, 0);
widgets.push(choice(i, poll.choices[i], votes, totalVotes, state));
}
return {
header: header(poll.topic, poll.author.displayName),
sections: [
{
widgets,
},
],
};
}
exports.buildVoteCard = buildVoteCard;
Việc triển khai tương tự như phương pháp được dùng với hộp thoại, mặc dù mã đánh dấu cho thẻ tương tác hơi khác so với hộp thoại. Như trước đây, bạn có thể xem mẫu JSON được tạo trong công cụ Trình tạo thẻ.
Triển khai thao tác bình chọn
Thẻ bình chọn có một nút cho mỗi lựa chọn. Chỉ mục của lựa chọn đó, cùng với trạng thái được chuyển đổi tuần tự của cuộc thăm dò ý kiến, sẽ được đính kèm vào nút. Ứng dụng nhận được một CARD_CLICKED
có thao tác vote
cùng với mọi dữ liệu được đính kèm vào nút dưới dạng tham số.
Cập nhật index.js
bằng:
const { buildConfigurationForm, MAX_NUM_OF_OPTIONS } = require('./config-form');
const { buildVoteCard } = require('./vote-card');
/**
* App entry point.
*/
exports.app = async (req, res) => {
if (!(req.method === 'POST' && req.body)) {
res.status(400).send('')
}
const event = req.body;
let reply = {};
// Dispatch slash and action events
if (event.type === 'MESSAGE') {
const message = event.message;
if (message.slashCommand?.commandId === '1') {
reply = showConfigurationForm(event);
}
} else if (event.type === 'CARD_CLICKED') {
if (event.action?.actionMethodName === 'start_poll') {
reply = await startPoll(event);
} else if (event.action?.actionMethodName === 'vote') {
reply = recordVote(event);
}
}
res.json(reply);
}
/**
* Handles the slash command to display the config form.
*
* @param {object} event - chat event
* @returns {object} Response to send back to Chat
*/
function showConfigurationForm(event) {
// Seed the topic with any text after the slash command
const topic = event.message?.argumentText?.trim();
const dialog = buildConfigurationForm({
topic,
choices: [],
});
return {
actionResponse: {
type: 'DIALOG',
dialogAction: {
dialog: {
body: dialog,
},
},
},
};
}
/**
* Handle the custom start_poll action.
*
* @param {object} event - chat event
* @returns {object} Response to send back to Chat
*/
function startPoll(event) {
// Not fully implemented yet -- just close the dialog
return {
actionResponse: {
type: 'DIALOG',
dialogAction: {
actionStatus: {
statusCode: 'OK',
userFacingMessage: 'Poll started.',
},
},
},
}
}
/**
* Handle the custom vote action. Updates the state to record
* the user's vote then rerenders the card.
*
* @param {object} event - chat event
* @returns {object} Response to send back to Chat
*/
function recordVote(event) {
const parameters = event.common?.parameters;
const choice = parseInt(parameters['index']);
const userId = event.user.name;
const state = JSON.parse(parameters['state']);
// Add or update the user's selected option
state.votes[userId] = choice;
const card = buildVoteCard(state);
return {
thread: event.message.thread,
actionResponse: {
type: 'UPDATE_MESSAGE',
},
cards: [card],
}
}
Phương thức recordVote
phân tích cú pháp trạng thái đã lưu và cập nhật trạng thái đó bằng phiếu bầu của người dùng, sau đó kết xuất lại thẻ. Kết quả của cuộc thăm dò được chuyển đổi tuần tự và lưu trữ cùng với thẻ mỗi khi thẻ được cập nhật.
Kết nối các mảnh
Ứng dụng này sắp hoàn tất. Sau khi triển khai lệnh dấu gạch chéo cùng với tính năng bỏ phiếu, việc duy nhất còn lại là hoàn tất phương thức startPoll
.
Nhưng có một vấn đề.
Khi cấu hình cuộc thăm dò được gửi, ứng dụng cần thực hiện 2 thao tác:
- Đóng hộp thoại.
- Đăng một tin nhắn mới vào không gian có thẻ biểu quyết.
Rất tiếc, phản hồi trực tiếp cho yêu cầu HTTP chỉ có thể thực hiện một yêu cầu và yêu cầu đó phải là yêu cầu đầu tiên. Để đăng thẻ bình chọn, ứng dụng phải sử dụng Chat API để tạo một tin nhắn mới không đồng bộ.
Thêm thư viện ứng dụng
Chạy lệnh sau để cập nhật các phần phụ thuộc của ứng dụng nhằm thêm ứng dụng Google API cho Node.js.
npm install --save googleapis
Bắt đầu cuộc thăm dò ý kiến
Cập nhật index.js
lên phiên bản cuối cùng dưới đây:
const { buildConfigurationForm, MAX_NUM_OF_OPTIONS } = require('./config-form');
const { buildVoteCard } = require('./vote-card');
const {google} = require('googleapis');
/**
* App entry point.
*/
exports.app = async (req, res) => {
if (!(req.method === 'POST' && req.body)) {
res.status(400).send('')
}
const event = req.body;
let reply = {};
// Dispatch slash and action events
if (event.type === 'MESSAGE') {
const message = event.message;
if (message.slashCommand?.commandId === '1') {
reply = showConfigurationForm(event);
}
} else if (event.type === 'CARD_CLICKED') {
if (event.action?.actionMethodName === 'start_poll') {
reply = await startPoll(event);
} else if (event.action?.actionMethodName === 'vote') {
reply = recordVote(event);
}
}
res.json(reply);
}
/**
* Handles the slash command to display the config form.
*
* @param {object} event - chat event
* @returns {object} Response to send back to Chat
*/
function showConfigurationForm(event) {
// Seed the topic with any text after the slash command
const topic = event.message?.argumentText?.trim();
const dialog = buildConfigurationForm({
topic,
choices: [],
});
return {
actionResponse: {
type: 'DIALOG',
dialogAction: {
dialog: {
body: dialog,
},
},
},
};
}
/**
* Handle the custom start_poll action.
*
* @param {object} event - chat event
* @returns {object} Response to send back to Chat
*/
async function startPoll(event) {
// Get the form values
const formValues = event.common?.formInputs;
const topic = formValues?.['topic']?.stringInputs.value[0]?.trim();
const choices = [];
for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
const choice = formValues?.[`option${i}`]?.stringInputs.value[0]?.trim();
if (choice) {
choices.push(choice);
}
}
if (!topic || choices.length === 0) {
// Incomplete form submitted, rerender
const dialog = buildConfigurationForm({
topic,
choices,
});
return {
actionResponse: {
type: 'DIALOG',
dialogAction: {
dialog: {
body: dialog,
},
},
},
};
}
// Valid configuration, build the voting card to display
// in the space
const pollCard = buildVoteCard({
topic: topic,
author: event.user,
choices: choices,
votes: {},
});
const message = {
cards: [pollCard],
};
const request = {
parent: event.space.name,
requestBody: message,
};
// Use default credentials (service account)
const credentials = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/chat.bot'],
});
const chatApi = google.chat({
version: 'v1',
auth: credentials,
});
await chatApi.spaces.messages.create(request);
// Close dialog
return {
actionResponse: {
type: 'DIALOG',
dialogAction: {
actionStatus: {
statusCode: 'OK',
userFacingMessage: 'Poll started.',
},
},
},
};
}
/**
* Handle the custom vote action. Updates the state to record
* the user's vote then rerenders the card.
*
* @param {object} event - chat event
* @returns {object} Response to send back to Chat
*/
function recordVote(event) {
const parameters = event.common?.parameters;
const choice = parseInt(parameters['index']);
const userId = event.user.name;
const state = JSON.parse(parameters['state']);
// Add or update the user's selected option
state.votes[userId] = choice;
const card = buildVoteCard(state);
return {
thread: event.message.thread,
actionResponse: {
type: 'UPDATE_MESSAGE',
},
cards: [card],
}
}
Triển khai lại hàm:
gcloud functions deploy app --trigger-http --security-level=secure-always
Giờ đây, bạn có thể sử dụng đầy đủ các chức năng của ứng dụng. Hãy thử gọi lệnh /poll
để đưa ra một câu hỏi và một vài lựa chọn. Sau khi bạn gửi, thẻ thăm dò ý kiến sẽ xuất hiện.
Hãy bình chọn và xem điều gì sẽ xảy ra.
Tất nhiên, việc tự tạo cuộc thăm dò ý kiến không có nhiều tác dụng, vì vậy, hãy mời một số bạn bè hoặc đồng nghiệp dùng thử!
6. Xin chúc mừng
Xin chúc mừng! Bạn đã tạo và triển khai thành công một ứng dụng Google Chat bằng Cloud Functions. Mặc dù lớp học lập trình này đã đề cập đến nhiều khái niệm cốt lõi để xây dựng một ứng dụng, nhưng vẫn còn nhiều điều khác để khám phá. Hãy xem các tài nguyên bên dưới và nhớ dọn dẹp dự án của bạn để tránh bị tính thêm phí.
Các hoạt động khác
Nếu muốn tìm hiểu sâu hơn về nền tảng Chat và ứng dụng này, bạn có thể tự mình thử một số việc sau:
- Điều gì xảy ra khi bạn @ đề cập đến ứng dụng? Hãy thử cập nhật ứng dụng để cải thiện hành vi.
- Việc chuyển đổi trạng thái của cuộc thăm dò ý kiến thành dữ liệu nối tiếp trong thẻ là phù hợp với không gian nhỏ, nhưng có giới hạn. Hãy thử chuyển sang một lựa chọn tốt hơn.
- Nếu tác giả muốn chỉnh sửa hoặc ngừng nhận phiếu bầu mới thì sao? Bạn sẽ triển khai những tính năng đó như thế nào?
- Điểm cuối của ứng dụng chưa được bảo mật. Hãy thử thêm một số bước xác minh để đảm bảo các yêu cầu đến từ Google Chat.
Đây chỉ là một vài cách để cải thiện ứng dụng. Chúc bạn vui vẻ và thoả sức tưởng tượng!
Dọn dẹp
Để tránh phát sinh phí cho tài khoản Google Cloud Platform của bạn đối với các tài nguyên được dùng trong hướng dẫn này, hãy làm như sau:
- Trong Cloud Console, hãy chuyển đến trang Quản lý tài nguyên. Nhấp vào biểu tượng Ở góc trên cùng bên trái, hãy nhấp vào Trình đơn
> IAM và Quản trị > Quản lý tài nguyên.
- Trong danh sách dự án, hãy chọn dự án của bạn rồi nhấp vào Xoá.
- Trong hộp thoại, hãy nhập mã dự án rồi nhấp vào Tắt để xoá dự án.
Tìm hiểu thêm
Để biết thêm thông tin về cách phát triển ứng dụng Chat, hãy xem:
Để biết thêm thông tin về cách phát triển trong Google Cloud Console, hãy xem: