1. Wprowadzenie
Aplikacje Google Chat umożliwiają korzystanie z usług i zasobów bezpośrednio w Google Chat, dzięki czemu użytkownicy mogą uzyskiwać informacje i szybko podejmować działania bez opuszczania rozmowy.
Z tego przewodnika dowiesz się, jak utworzyć i wdrożyć aplikację do głosowania za pomocą Node.js i Cloud Functions.
Czego się nauczysz
- Korzystanie z Cloud Shell
- Wdrażanie w Cloud Functions
- Uzyskiwanie danych wejściowych od użytkowników za pomocą poleceń po ukośniku i okien
- Tworzenie interaktywnych kart
2. Konfiguracja i wymagania
Utwórz projekt Google Cloud, a następnie włącz interfejsy API i usługi, z których będzie korzystać aplikacja Chat.
Wymagania wstępne
Tworzenie aplikacji Google Chat wymaga konta Google Workspace z dostępem do Google Chat. Jeśli nie masz jeszcze konta Google Workspace, utwórz je i zaloguj się, zanim przejdziesz do tego laboratorium.
Samodzielne konfigurowanie środowiska
- Otwórz konsolę Google Cloud i utwórz projekt.
Zapamiętaj identyfikator projektu, czyli unikalną nazwę we wszystkich projektach Google Cloud (podana wyżej nazwa jest już zajęta i nie będzie działać, przepraszamy!). W dalszej części tego laboratorium będzie on nazywanyPROJECT_ID
.
- Następnie, aby korzystać z zasobów Google Cloud, włącz rozliczenia w konsoli Cloud.
Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Postępuj zgodnie z instrukcjami w sekcji „Usuwanie” na końcu ćwiczenia, która zawiera wskazówki dotyczące zamykania zasobów, aby uniknąć naliczania opłat po zakończeniu tego samouczka. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.
Google Cloud Shell
Google Cloud można obsługiwać zdalnie z laptopa, ale w tym module praktycznym użyjemy Google Cloud Shell, czyli środowiska wiersza poleceń działającego w Google Cloud.
Aktywowanie Cloud Shell
- W konsoli Cloud kliknij Aktywuj Cloud Shell
.
Gdy otworzysz Cloud Shell po raz pierwszy, zobaczysz opisową wiadomość powitalną. Jeśli zobaczysz wiadomość powitalną, kliknij Dalej. Wiadomość powitalna nie pojawi się ponownie. Oto wiadomość powitalna:
Udostępnienie Cloud Shell i połączenie się z nim powinno zająć tylko kilka chwil. Po nawiązaniu połączenia zobaczysz terminal Cloud Shell:
Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i uwierzytelnianie. Wszystkie czynności w tym samouczku możesz wykonać w przeglądarce lub na Chromebooku.Po połączeniu z Cloud Shell zobaczysz, że jesteś już uwierzytelniony(-a), a projekt jest już ustawiony na Twój identyfikator projektu. - Aby potwierdzić, że masz autoryzację, uruchom w Cloud Shell to polecenie:
Jeśli pojawi się pytanie o autoryzację Cloud Shell do wykonywania wywołań interfejsu API GCP, kliknij Autoryzuj.gcloud auth list
Dane wyjściowe polecenia Jeśli Twoje konto nie jest wybrane domyślnie, uruchom:Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com>
$ gcloud config set account <ACCOUNT>
- Sprawdź, czy wybrany został właściwy projekt. W Cloud Shell uruchom:
Wynik poleceniagcloud config list project
Jeśli nie otrzymasz prawidłowego projektu, możesz go ustawić za pomocą tego polecenia:[core] project = <PROJECT_ID>
Wynik poleceniagcloud config set project <PROJECT_ID>
Updated property [core/project].
Podczas wykonywania tego ćwiczenia będziesz używać operacji wiersza poleceń i edytować pliki. Aby edytować pliki, możesz użyć wbudowanego edytora kodu Cloud Shell, czyli edytora Cloud Shell. W tym celu kliknij Otwórz edytor po prawej stronie paska narzędzi Cloud Shell. W Cloud Shell dostępne są też popularne edytory, takie jak Vim i Emacs.
3. Włączanie interfejsów API Cloud Functions, Cloud Build i Google Chat
W Cloud Shell włącz te interfejsy API i usługi:
gcloud services enable \ cloudfunctions \ cloudbuild.googleapis.com \ chat.googleapis.com
Wykonanie tej operacji może potrwać kilka chwil.
Po zakończeniu pojawi się komunikat o powodzeniu podobny do tego:
Operation "operations/acf.cc11852d-40af-47ad-9d59-477a12847c9e" finished successfully.
4. Tworzenie początkowej aplikacji Google Chat
Inicjowanie projektu
Na początek utworzysz i wdrożysz prostą aplikację „Hello world”. Aplikacje do czatu to usługi internetowe, które odpowiadają na żądania HTTPS i zwracają ładunek JSON. W przypadku tej aplikacji użyjesz Node.js i Cloud Functions.
W Cloud Shell utwórz nowy katalog o nazwie poll-app
i przejdź do niego:
mkdir ~/poll-app cd ~/poll-app
Wszystkie pozostałe zadania w tym ćwiczeniu i utworzone przez Ciebie pliki będą znajdować się w tym katalogu.
Zainicjuj projekt Node.js:
npm init
NPM zadaje kilka pytań dotyczących konfiguracji projektu, np. nazwy i wersji. W przypadku każdego pytania naciśnij ENTER
, aby zaakceptować wartości domyślne. Domyślnym punktem wejścia jest plik o nazwie index.js
, który utworzymy w następnym kroku.
Tworzenie backendu aplikacji do obsługi czatu
Czas zacząć tworzyć aplikację. Utwórz plik o nazwie index.js
z tą treścią:
/**
* 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)
}
Aplikacja nie będzie jeszcze wiele robić, ale to nic. Więcej funkcji dodasz później.
Wdrażanie aplikacji
Aby wdrożyć aplikację „Hello world”, wdróż funkcję w Cloud Functions, skonfiguruj aplikację do czatu w konsoli Google Cloud i wyślij do niej wiadomość testową, aby sprawdzić wdrożenie.
Wdrażanie funkcji w Cloud Functions
Aby wdrożyć funkcję Cloud Function aplikacji „Hello world”, wpisz to polecenie:
gcloud functions deploy app --trigger-http --security-level=secure-always --allow-unauthenticated --runtime nodejs14
Po zakończeniu dane wyjściowe powinny wyglądać mniej więcej tak:
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'
Zanotuj adres URL wdrożonej funkcji we właściwości httpsTrigger.url
. Użyjesz go w następnym kroku.
Konfigurowanie aplikacji
Aby skonfigurować aplikację, otwórz stronę Konfiguracja Chatu w konsoli Cloud.
- Odznacz opcję Utwórz tę aplikację Google Chat jako dodatek do Workspace i kliknij WYŁĄCZ, aby potwierdzić.
- W polu Nazwa aplikacji wpisz „PollCodelab”.
- W polu URL awatara wpisz
https://raw.githubusercontent.com/google/material-design-icons/master/png/social/poll/materialicons/24dp/2x/baseline_poll_black_24dp.png
. - W sekcji Opis wpisz „Aplikacja do ankiet na potrzeby warsztatów”.
- W sekcji Funkcje wybierz Odbieranie wiadomości 1:1 i Dołączanie do pokoi i rozmów grupowych.
- W sekcji Ustawienia połączenia wybierz Adres URL punktu końcowego HTTP i wklej adres URL funkcji Cloud Function (właściwość
httpsTrigger.url
z ostatniej sekcji). - W sekcji Uprawnienia kliknij Określeni użytkownicy i grupy w Twojej domenie i wpisz swój adres e-mail.
- Kliknij Zapisz.
Aplikacja jest teraz gotowa do wysyłania wiadomości.
Testowanie aplikacji
Zanim przejdziesz dalej, sprawdź, czy aplikacja działa, dodając ją do pokoju w Google Chat.
- Otwórz Google Chat.
- Obok opcji Czat kliknij + > Znajdź aplikacje.
- Wpisz „PollCodelab” w wyszukiwarce.
- Kliknij Czat.
- Aby wysłać wiadomość do aplikacji, wpisz „Hello” i naciśnij Enter.
Aplikacja powinna odpowiedzieć krótką wiadomością powitalną.
Teraz, gdy mamy już podstawową strukturę, czas przekształcić ją w coś bardziej przydatnego.
5. Tworzenie funkcji ankiety
Krótki opis działania aplikacji
Aplikacja składa się z 2 głównych części:
- Polecenie ze znakiem ukośnika, które wyświetla okno dialogowe do konfigurowania ankiety.
- Interaktywna karta do głosowania i wyświetlania wyników.
Aplikacja potrzebuje też stanu, w którym będzie przechowywać konfigurację ankiety i jej wyniki. Można to zrobić za pomocą Firestore lub dowolnej bazy danych albo stan może być przechowywany w samych wiadomościach aplikacji. Ta aplikacja jest przeznaczona do przeprowadzania szybkich, nieformalnych ankiet wśród zespołu, więc przechowywanie stanu w wiadomościach aplikacji sprawdza się w tym przypadku.
Model danych aplikacji (wyrażony w języku TypeScript) wygląda tak:
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 };
}
Oprócz tematu lub pytania i listy opcji stan zawiera identyfikator i nazwę autora oraz zarejestrowane głosy. Aby zapobiec wielokrotnemu głosowaniu przez użytkowników, głosy są przechowywane jako mapa identyfikatorów użytkowników do indeksu wybranej opcji.
Oczywiście istnieje wiele różnych podejść, ale to jest dobry punkt wyjścia do przeprowadzania szybkich ankiet w pokoju.
Wdrażanie polecenia konfiguracji sondy
Aby umożliwić użytkownikom rozpoczynanie i konfigurowanie ankiet, skonfiguruj polecenie ze znakiem ukośnika, które otwiera okno. Jest to proces wieloetapowy:
- Zarejestruj polecenie po ukośniku, które rozpoczyna ankietę.
- Utwórz okno, w którym będzie można skonfigurować ankietę.
- Pozwól aplikacji rozpoznać i obsłużyć polecenie po ukośniku.
- Twórz interaktywne karty, które ułatwiają głosowanie w ankiecie.
- Wdróż kod, który umożliwia aplikacji przeprowadzanie ankiet.
- Ponownie wdróż funkcję w Cloud Functions.
Rejestrowanie polecenia po ukośniku
Aby zarejestrować polecenie ze znakiem ukośnika, wróć do strony Konfiguracja czatu w konsoli (Interfejsy API i usługi > Panel > Interfejs Hangouts Chat API > Konfiguracja).
- W sekcji Polecenia po ukośniku kliknij Dodaj nowe polecenie po ukośniku.
- W polu Nazwa wpisz „/poll”.
- W polu Identyfikator polecenia wpisz „1”.
- W polu Opis wpisz „Rozpocznij ankietę”.
- Kliknij Otwiera okno.
- Kliknij Gotowe.
- Kliknij Zapisz.
Aplikacja rozpozna teraz polecenie /poll
i otworzy okno. Następnie skonfigurujmy okno.
Tworzenie formularza konfiguracji jako okna
Polecenie po ukośniku otwiera okno, w którym możesz skonfigurować temat ankiety i możliwe odpowiedzi. Utwórz nowy plik o nazwie config-form.js
z tą zawartością:
/** 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;
Ten kod generuje formularz okna, który umożliwia użytkownikowi skonfigurowanie ankiety. Eksportuje też stałą określającą maksymalną liczbę odpowiedzi, które może mieć pytanie. Dobrym rozwiązaniem jest wyodrębnienie tworzenia kodu interfejsu do funkcji bezstanowych, w których stan jest przekazywany jako parametr. Ułatwia to ponowne wykorzystanie karty, która będzie później renderowana w różnych kontekstach.
Ta implementacja rozkłada też kartę na mniejsze jednostki lub komponenty. Nie jest to wymagane, ale ta technika jest zalecana, ponieważ w przypadku tworzenia złożonych interfejsów jest zwykle bardziej czytelna i łatwiejsza w utrzymaniu.
Aby zobaczyć przykładowy kompletny plik JSON, który tworzy, wyświetl go w narzędziu do tworzenia kart.
Obsługa polecenia po ukośniku
Polecenia z ukośnikiem są wysyłane do aplikacji jako zdarzenia MESSAGE
. Zaktualizuj index.js
, aby sprawdzać obecność polecenia z ukośnikiem za pomocą zdarzenia MESSAGE
i odpowiadać za pomocą okna. Zastąp index.js
tymi elementami:
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.',
},
},
},
}
}
Aplikacja będzie teraz wyświetlać okno po wywołaniu polecenia /poll
. Przetestuj interakcję, ponownie wdrażając funkcję Cloud z Cloud Shell.
gcloud functions deploy app --trigger-http --security-level=secure-always
Po wdrożeniu funkcji utworzonej w Cloud Functions wyślij do aplikacji wiadomość z poleceniem /poll
, aby przetestować polecenie ukośnika i okno dialogowe. Okno wysyła CARD_CLICKED
zdarzenie z działaniem niestandardowym start_poll
. Zdarzenie jest obsługiwane w zaktualizowanym punkcie wejścia, w którym wywołuje metodę startPoll
. Obecnie metoda startPoll
jest tylko szkieletem, który zamyka okno. W następnej sekcji zaimplementujesz funkcję głosowania i połączysz wszystkie elementy.
Wdrożenie karty do głosowania
Aby wdrożyć część aplikacji związaną z głosowaniem, zacznij od zdefiniowania interaktywnej karty, która będzie interfejsem do głosowania.
Zaimplementuj interfejs głosowania
Utwórz plik o nazwie vote-card.js
z tą zawartością:
/**
* 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;
Implementacja jest podobna do podejścia zastosowanego w przypadku okna, chociaż kod znaczników kart interaktywnych nieco różni się od kodu okien. Jak wcześniej, możesz wyświetlić próbkę wygenerowanego kodu JSON w narzędziu do tworzenia kart.
Wdrożenie działania głosowania
Karta do głosowania zawiera przycisk dla każdej opcji. Do przycisku dołączony jest indeks wybranej opcji oraz zserializowany stan ankiety. Aplikacja otrzymuje CARD_CLICKED
z działaniem vote
oraz wszystkie dane dołączone do przycisku jako parametry.
Zaktualizuj index.js
za pomocą:
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],
}
}
Metoda recordVote
analizuje zapisany stan i aktualizuje go o głos użytkownika, a następnie ponownie renderuje kartę. Wyniki ankiety są serializowane i zapisywane na karcie przy każdej aktualizacji.
Łączenie elementów
Aplikacja jest prawie gotowa. Po zaimplementowaniu polecenia ukośnika wraz z głosowaniem pozostało tylko dokończenie metody startPoll
.
Ale jest pewien haczyk.
Po przesłaniu konfiguracji ankiety aplikacja musi wykonać 2 działania:
- Zamknij okno.
- Opublikuj w pokoju nową wiadomość z kartą do głosowania.
Niestety bezpośrednia odpowiedź na żądanie HTTP może wykonać tylko jedną czynność i musi być pierwszą. Aby opublikować kartę do głosowania, aplikacja musi użyć interfejsu Chat API do asynchronicznego utworzenia nowej wiadomości.
Dodawanie biblioteki klienta
Uruchom to polecenie, aby zaktualizować zależności aplikacji i dodać do nich klienta interfejsu Google API dla Node.js.
npm install --save googleapis
Rozpocznij ankietę
Zaktualizuj urządzenie index.js
do ostatecznej wersji podanej poniżej:
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],
}
}
Ponownie wdróż funkcję:
gcloud functions deploy app --trigger-http --security-level=secure-always
Teraz możesz w pełni korzystać z aplikacji. Wypróbuj polecenie /poll
, zadając pytanie i podając kilka opcji. Po przesłaniu pojawi się karta ankiety.
Oddaj głos i zobacz, co się stanie.
Oczywiście głosowanie na siebie nie jest zbyt przydatne, więc zaproś znajomych lub współpracowników, aby wypróbowali tę funkcję.
6. Gratulacje
Gratulacje! Udało Ci się utworzyć i wdrożyć aplikację Google Chat za pomocą Cloud Functions. Ćwiczenia z programowania obejmowały wiele podstawowych koncepcji tworzenia aplikacji, ale jest jeszcze wiele do odkrycia. Zapoznaj się z materiałami poniżej i nie zapomnij wyczyścić projektu, aby uniknąć dodatkowych opłat.
Dodatkowe działania
Jeśli chcesz lepiej poznać platformę Google Chat i tę aplikację, możesz samodzielnie wypróbować te funkcje:
- Co się stanie, gdy użyjesz @ wzmianki o aplikacji? Spróbuj zaktualizować aplikację, aby poprawić jej działanie.
- Serializowanie stanu ankiety w karcie jest w porządku w przypadku małych przestrzeni, ale ma pewne ograniczenia. Spróbuj przełączyć się na lepszą opcję.
- Co zrobić, jeśli autor chce edytować ankietę lub przestać przyjmować nowe głosy? Jak wdrożysz te funkcje?
- Punkt końcowy aplikacji nie jest jeszcze zabezpieczony. Spróbuj dodać weryfikację, aby mieć pewność, że żądania pochodzą z Google Chat.
To tylko kilka sposobów na ulepszenie aplikacji. Baw się dobrze i wykorzystaj swoją wyobraźnię!
Czyszczenie danych
Aby uniknąć obciążenia konta Google Cloud Platform opłatami za zasoby zużyte w tym samouczku:
- W Cloud Console otwórz stronę Zarządzanie zasobami. W lewym górnym rogu kliknij Menu
Administracja > Zarządzaj zasobami.
- Na liście projektów wybierz projekt, a następnie kliknij Usuń.
- W oknie wpisz identyfikator projektu i kliknij Wyłącz, aby usunąć projekt.
Więcej informacji
Więcej informacji o tworzeniu aplikacji do obsługi czatu znajdziesz w tych artykułach:
Więcej informacji o programowaniu w konsoli Google Cloud znajdziesz w tych artykułach: