Créer une application de sondage interactive pour Google Chat avec Node.js

1. Présentation

Avec les applications Google Chat, les services et les ressources sont directement intégrés à Google Chat, ce qui permet aux utilisateurs d'obtenir des informations et d'agir rapidement sans quitter la conversation.

Dans cet atelier de programmation, vous allez apprendre à créer et à déployer une application de sondage sur Node.js et Cloud Functions.

Ce que vous allez apprendre

  • Utiliser Cloud Shell
  • Déployer une application dans Cloud Functions
  • Recevoir les entrées utilisateur à l'aide des commandes à barre oblique et des boîtes de dialogue
  • Création de fiches interactives

2. Prérequis

Créez un projet Google Cloud, puis activez les API et les services que l'application Chat utilisera

Prérequis

Pour développer une application Google Chat, vous devez disposer d'un compte Google Workspace ayant accès à Google Chat. Si vous ne possédez pas encore de compte Google Workspace, créez-en un et connectez-vous avant de poursuivre cet atelier de programmation.

Configuration de l'environnement au rythme de chacun

  1. Ouvrez leGoogle Cloud Console et créez uneprojet dans le menu déroulant ;

    Menu de sélection de projetBouton "Nouveau projet"ID du projet

    Notez l'ID du projet, un nom unique parmi tous les projets Google Cloud (le nom ci-dessus a déjà été utilisé et ne fonctionnera pas pour vous, malheureusement !). Nous l'appellerons plus tard PROJECT_ID dans cet atelier de programmation.
  1. Ensuite, pour utiliser les ressources Google Cloud, activez la facturation dans Cloud Console.

L'exécution de cet atelier de programmation est très peu coûteuse, voire gratuite. Veillez à suivre les instructions de la section "Effacer" à la fin de l'atelier de programmation qui vous expliquent comment arrêter les ressources afin d'éviter toute facturation au-delà de ce tutoriel. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai gratuit pour bénéficier d'un crédit de 300 $.

Google Cloud Shell

Vous pouvez utiliser Google Cloud à distance depuis votre ordinateur portable. Dans cet atelier de programmation, nous utiliserons Google Cloud Shell, un environnement de ligne de commande fonctionnant dans Google Cloud.

Activer Cloud Shell

  1. Dans Cloud Console, cliquez sur Activer Cloud Shell Icône Cloud Shell.

    Icône Cloud Shell dans la barre de menu

    La première fois que vous ouvrez Cloud Shell, un message de bienvenue descriptif s'affiche. Si le message de bienvenue s'affiche, cliquez sur Continuer. Le message de bienvenue n'apparaît plus. Voici le message de bienvenue:

    Message de bienvenue de Cloud Shell

    Cela ne devrait prendre que quelques instants pour provisionner Cloud Shell et s'y connecter. Une fois connecté, le terminal Cloud Shell s'affiche:

    Le terminal Cloud Shell

    Cette machine virtuelle contient tous les outils de développement dont vous avez besoin. Elle intègre un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances réseau et l'authentification. Toutes les tâches de cet atelier de programmation peuvent être effectuées à l'aide d'un navigateur ou de votre Chromebook.Une fois connecté à Cloud Shell, vous devriez constater que vous êtes déjà authentifié et que le projet est déjà défini sur votre ID de projet.
  2. Exécutez la commande suivante dans Cloud Shell pour vérifier que vous êtes authentifié :
    gcloud auth list
    
    Si vous êtes invité à autoriser Cloud Shell à effectuer un appel d'API GCP, cliquez sur Autoriser.

    Résultat de la commande
    Credentialed Accounts
    ACTIVE  ACCOUNT
    *       <my_account>@<my_domain.com>
    
    Si votre compte n'est pas sélectionné par défaut, exécutez la commande suivante:
    $ gcloud config set account <ACCOUNT>
    
  1. Confirmez que vous avez sélectionné le bon projet. Dans Cloud Shell, exécutez la commande suivante:
    gcloud config list project
    
    Résultat de la commande
    [core]
    project = <PROJECT_ID>
    
    Si aucun projet n'est renvoyé, vous pouvez le définir à l'aide de la commande suivante:
    gcloud config set project <PROJECT_ID>
    
    Résultat de la commande
    Updated property [core/project].
    

À la fin de cet atelier de programmation, vous utiliserez les opérations de ligne de commande et modifierez des fichiers. Pour modifier des fichiers, vous pouvez utiliser l'éditeur de code intégré à Cloud Shell, en cliquant sur Ouvrir l'éditeur sur la droite de la barre d'outils Cloud Shell. Des éditeurs populaires comme Vim et Emacs sont également disponibles dans Cloud Shell.

3. Activer Cloud Functions, Cloud Build et les API Google Chat

Dans Cloud Shell, activez les API et les services suivants:

gcloud services enable \
  cloudfunctions \
  cloudbuild.googleapis.com \
  chat.googleapis.com

Cette opération peut prendre quelques instants.

Une fois l'opération terminée, un message semblable à celui-ci s'affiche:

Operation "operations/acf.cc11852d-40af-47ad-9d59-477a12847c9e" finished successfully.

4. Créer l'application Chat initiale

Initialiser le projet

Pour commencer, vous allez créer et déployer une application "Hello World" simple. Les applications de chat sont des services Web qui répondent aux requêtes HTTPS et envoient une charge utile JSON. Pour cette application, vous allez utiliser Node.js et Cloud Functions.

Dans Cloud Shell, créez un répertoire nommé poll-app et accédez-y:

mkdir ~/poll-app
cd ~/poll-app

Tout le travail restant pour cet atelier de programmation et les fichiers que vous créerez seront stockés dans ce répertoire.

Initialisez le projet Node.js:

npm init

La gestion des partenariats stratégiques pose plusieurs questions sur la configuration du projet, telles que son nom et sa version. Pour chaque question, appuyez sur ENTER pour accepter les valeurs par défaut. Le point d'entrée par défaut est un fichier nommé index.js, que nous allons créer ensuite.

Créer le backend de l'application Chat

Il est temps de commencer à créer l'application. Créez un fichier nommé index.js avec le contenu suivant:

/**
 * 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)
}

L'application ne fera que peu de choses, mais ce n'est pas grave. Vous ajouterez des fonctionnalités plus tard.

Déployer l'application

Pour déployer l'application "Hello world", vous allez déployer la fonction Cloud, configurer l'application Chat dans Google Cloud Console et envoyer un message de test à l'application pour vérifier le déploiement.

Déployer la fonction Cloud

Pour déployer la fonction Cloud de l'application"Hello World", saisissez la commande suivante:

gcloud functions deploy app --trigger-http --security-level=secure-always --allow-unauthenticated --runtime nodejs14

Une fois l'opération terminée, le résultat devrait ressembler à ceci:

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'

Notez l'URL de la fonction déployée dans la propriété httpsTrigger.url. Vous en aurez besoin à l'étape suivante.

Configurer l'application

Pour configurer l'application, accédez àConfiguration du chat Page dans Cloud Console (API et services >Tableau de bord >API Hangouts Chat >Configuration ).

  1. Dans App name (Nom de l'application), saisissez "PollCodelab".
  2. Dans le champ URL de l'avatar, saisissez https://raw.githubusercontent.com/google/material-design-icons/master/png/social/poll/materialicons/24dp/2x/baseline_poll_black_24dp.png.
  3. Dans Description, saisissez "Sondage de l'application pour l'atelier de programmation".
  4. Dans la section Functionality (Fonctionnalité), sélectionnez App can Immersion directe par message et L'application fonctionne dans les espaces comptant plusieurs utilisateurs.
  5. Sous Paramètres de connexion, sélectionnez URL de l'application, puis collez l'URL de la fonction Cloud (la propriété httpsTrigger.url utilisée à la section précédente).
  6. Sous Autorisations, sélectionnez Personnes et groupes spécifiques dans votre domaine, puis saisissez votre adresse e-mail.
  7. Cliquez sur Save (Enregistrer).

L'application est prête à envoyer des messages.

Tester l'application

Avant de poursuivre, vérifiez que l'application fonctionne en l'ajoutant à un espace dans Google Chat.

  1. Accédez à Google Chat.
  2. À côté de Chat, cliquez sur + > Rechercher des applications.
  3. Saisissez "PollCodelab" dans la recherche.
  4. Cliquez sur Chat.
  5. Pour envoyer un message à l'application, saisissez "Hello" et appuyez sur Entrée.

L'application devrait répondre par un message de bienvenue.

Maintenant qu'un squelette de base est en place, il est temps de le transformer en un objet plus utile.

5. Créer les fonctionnalités de sondage

Présentation rapide du fonctionnement de l'application

L'application se compose de deux parties principales:

  1. Commande à barre oblique qui affiche une boîte de dialogue pour configurer le sondage.
  2. Fiche interactive pour voter et afficher les résultats.

L'application requiert également un état pour stocker la configuration du sondage et les résultats. Cela peut être fait avec Firestore ou n'importe quelle base de données, ou l'état peut être stocké dans les messages de l'application eux-mêmes. Étant donné que cette application est conçue pour réaliser des sondages rapides et informels d'une équipe, ce stockage est très efficace dans ce cas d'utilisation.

Le modèle de données de l'application (exprimé en typescript) est le suivant:

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 };
}

Outre le sujet ou la question et la liste des choix, l'État inclut l'ID et le nom de l'auteur, ainsi que les votes enregistrés. Pour éviter que les utilisateurs ne votent plusieurs fois, les votes sont stockés sous forme de carte des ID utilisateur à l'index de leur choix.

Il existe bien sûr de nombreuses approches différentes, mais elles offrent un bon point de départ pour exécuter des sondages rapides dans un espace.

Implémenter la commande de configuration de sondage

Pour permettre aux utilisateurs de démarrer et de configurer des sondages, configurez une commande à barre oblique qui ouvre une boîte de dialogue. Ce processus comporte plusieurs étapes:

  1. Enregistrez la commande à barre oblique qui lance un sondage.
  2. Créez la boîte de dialogue qui configure un sondage.
  3. Autorisez l'application à reconnaître et à gérer la commande à barre oblique.
  4. Créez des fiches interactives qui facilitent le vote dans le sondage.
  5. Implémentez le code qui permet à l'application d'exécuter des sondages.
  6. redéployez la fonction Cloud.

Enregistrer la commande à barre oblique

Pour enregistrer une commande à barre oblique, revenez à la page Configuration de Chat de la console (API et services > Tableau de bord > API Hangouts Chat). > Configuration.

  1. Sous Commandes à barre oblique, cliquez sur Ajouter une commande à barre oblique.
  2. Dans le champ Nom, saisissez "/poll"
  3. Dans le champ ID de commande, saisissez "1".
  4. Dans Description, saisissez "Start a sondage".
  5. Sélectionnez Ouvre une boîte de dialogue.
  6. Cliquez sur OK.
  7. Cliquez sur Save (Enregistrer).

L'application reconnaît maintenant la commande /poll et ouvre une boîte de dialogue. Configurez ensuite la boîte de dialogue.

Créer le formulaire de configuration en tant que boîte de dialogue

La commande à barre oblique ouvre une boîte de dialogue pour configurer le sujet du sondage et les choix possibles. Créez un fichier nommé config-form.js avec le contenu suivant:

/** 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;

Ce code génère le formulaire de boîte de dialogue qui permet à l'utilisateur de configurer le sondage. Il exporte également une constante pour le nombre maximal de choix dont dispose une question. Il est recommandé d'isoler la création de la balise UI dans des fonctions sans état, avec n'importe quel état transmis en tant que paramètres. Cela facilite la réutilisation, puis le rendu de cette fiche dans différents contextes.

Cette opération décompose également la carte en unités ou composants plus petits. Bien que cela ne soit pas obligatoire, il est recommandé d'utiliser cette technique, car elle est généralement plus lisible et facile à gérer lors de la création d'interfaces complexes.

Pour voir un exemple de code JSON complet créé, consultez-le dans l'outil Card Builder.

Gérer la commande à barre oblique

Les commandes à barre oblique s'affichent sous la forme d'événements MESSAGE lorsqu'elles sont envoyées à l'application. Mettez à jour index.js pour vérifier la présence d'une commande à barre oblique via un événement MESSAGE et répondre avec une boîte de dialogue. Remplacez index.js par ce qui suit:

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.',
        },
      },
    },
  }
}

L'application affiche désormais une boîte de dialogue lorsque la commande /poll est appelée. Testez l'interaction en exécutant la fonction Cloud dans Cloud Shell.

gcloud functions deploy app --trigger-http --security-level=secure-always

Une fois la fonction Cloud déployée, envoyez un message à l'application avec la commande /poll pour tester la commande à barre oblique et la boîte de dialogue. La boîte de dialogue envoie un événement CARD_CLICKED avec l'action personnalisée start_poll. L'événement est géré dans le point d'entrée mis à jour où il appelle la méthode startPoll. Pour l'instant, la méthode startPoll est bouchonnée pour fermer la boîte de dialogue. Dans la section suivante, vous allez implémenter la fonctionnalité de vote et connecter toutes les parties.

Mettre en œuvre la carte de vote

Pour mettre en œuvre la section de vote de l'application, commencez par définir la fiche interactive qui fournit une interface permettant aux utilisateurs de voter.

Implémenter l'interface de vote

Créez un fichier nommé vote-card.js avec le contenu suivant :

/**
 * 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;

L'implémentation est semblable à celle qui est adoptée avec la boîte de dialogue, bien que le balisage des fiches interactives soit légèrement différent des boîtes de dialogue. Comme auparavant, vous pouvez afficher un exemple de fichier JSON généré dans l'outil Card Builder.

Mettre en œuvre l'action de vote

La fiche de vote comprend un bouton pour chaque choix. L'index de ce choix et l'état sérialisé du sondage sont associés au bouton. L'application reçoit un CARD_CLICKED avec l'action vote et toutes les données associées au bouton comme paramètres.

Mettez à jour index.js avec:

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],
  }
}

La méthode recordVote analyse l'état stocké et la met à jour avec le vote de l'utilisateur, puis affiche à nouveau la carte. Les résultats du sondage sont sérialisés et stockés avec la fiche à chaque mise à jour.

Connecter les pièces

L'application est presque terminée. Avec la commande à barre oblique intégrée avec le vote, il ne vous reste plus qu'à terminer la méthode startPoll.

Mais il y a un inconvénient.

Lorsque la configuration des sondages est envoyée, deux actions sont nécessaires:

  1. Fermez la boîte de dialogue.
  2. Publier un nouveau message dans l'espace avec la fiche de vote.

Malheureusement, la réponse directe à la requête HTTP ne peut être effectuée que sur cette dernière et doit être la première. Pour publier la fiche de vote, l'application doit utiliser l'API Chat afin de créer un message de façon asynchrone.

Ajouter la bibliothèque cliente

Exécutez la commande suivante pour mettre à jour les dépendances de l'application afin d'inclure le client API Google pour Node.js.

npm install --save googleapis

Démarrer le sondage

Mettez à jour index.js vers la version finale ci-dessous:

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],
  }
}

Redéployez la fonction:

gcloud functions deploy app --trigger-http --security-level=secure-always

Vous devriez maintenant être en mesure de faire du sport dans l'app. Essayez d'appeler la commande /poll pour fournir une question et quelques options. La fiche du sondage s'affiche après l'envoi.

Votez et découvrez ce qu'il se passe.

Bien sûr, il n'est pas très utile de réaliser vous-même un sondage. Invitez des amis ou des collègues à vous lancer !

6. Félicitations

Félicitations ! Vous venez de créer et de déployer une application Google Chat à l'aide de Cloud Functions. L'atelier de programmation a traité de nombreux concepts fondamentaux de la création d'une application. Il reste toutefois beaucoup à découvrir. Consultez les ressources ci-dessous et n'oubliez pas de nettoyer votre projet pour éviter des frais supplémentaires.

Autres activités

Si vous souhaitez explorer la plate-forme Chat et cette application plus en détail, voici quelques suggestions pour résoudre le problème:

  • Que se passe-t-il lorsque vous mentionnez l'application à l'aide de la mention "@" ? Essayez de mettre à jour l'application pour améliorer le comportement.
  • La sérialisation de l'état du sondage dans la fiche est acceptable pour les petits espaces, mais présente des limites. Essayez de proposer une meilleure option.
  • Que se passe-t-il si l'auteur souhaite modifier le sondage ou ne plus accepter de nouveaux votes ? Comment intégreriez-vous ces fonctionnalités ?
  • Le point de terminaison de l'application n'est pas encore sécurisé. Effectuez une vérification pour vous assurer que les requêtes proviennent de Google Chat.

Voici quelques méthodes pour améliorer votre application. Amusez-vous et laissez libre cours à votre imagination !

Effectuer un nettoyage

Afin d'éviter la facturation sur votre compte Google Cloud Platform des ressources utilisées dans ce tutoriel, procédez comme suit :

  • Dans Cloud Console, accédez à la page Gérer les ressources. En haut à gauche, cliquez sur Menuicône de menu > IAM et administration > Gérer les ressources.
  1. Dans la liste des projets, sélectionnez votre projet, puis cliquez sur Supprimer.
  2. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour le supprimer.

Learn more

Pour en savoir plus sur le développement d'applications Chat, consultez les articles suivants:

Pour en savoir plus sur le développement dans Google Cloud Console, consultez les pages suivantes: