user16073125
user16073125

Reputation:

Dialogflow to Firestore using Inline Fulfillment - Store all user data in one document

How do we store all user input data in one document per one chat session?

I tried this code:

'use strict';

const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

process.env.DEBUG = 'dialogflow:debug';

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
    const agent = new WebhookClient({ request, response });

    function getAge(agent) {
        let age = agent.parameters.age;
        db.collection("users").add({age: age});
    }

    function getLocation(agent) {
        let location = agent.parameters.location;
        db.collection("users").add({location: location});
    }

    function getCustomerExperience(agent) {
        let customerExperience = agent.query;
        db.collection("users").add({customerExperience: customerExperience});
    }
  
    let intentMap = new Map();
    intentMap.set('age', age);
    intentMap.set('location', getLocation);
    intentMap.set('customer-experience', getCustomerExperience);
    agent.handleRequest(intentMap);
});

but the data were stored in different document IDs:

Data stored in different IDs

What I'm trying to achieve is something like this:

Data stored in the same ID

If I'm not being clear, please let me know. I'm new to Dialogflow, Firebase, as well as the JS language. Cheers!

Upvotes: 1

Views: 510

Answers (1)

Prisoner
Prisoner

Reputation: 50701

You're on the right track! The fundamental problem with your original code is that collection.add() will create a new document. But you want it to create a new document sometimes, and save it in a previous document other times.

This means that, during the entire Dialogflow session, you'll need some way to know what the document name is or should be. There are a few possible ways to do this.

Use a document based on the session

Dialogflow provides a session identifier that you can get as part of the agent.session property using the dialogflow-fulfillment library, or in the session property if you're parsing the JSON request body directly.

However, this string includes forward slash / characters, which should be avoided in document names. Fortunately, the format of this string is documented to be one of the two formats:

  • projects/Project ID/agent/sessions/Session ID
  • projects/Project ID/agent/environments/Environment ID/users/User ID/sessions/Session ID

In each case, the Session ID is the last portion of this path, so you can probably use code something like this to get the ID for the session, use it as your document name, and then save an attribute (for example, age) for it:

  function documentRef( agent ){
    const elements = agent.session.split('/');
    const lastElement = elements[elements.length - 1];
    return db.collection('users').doc(lastElement);
  }

  async function getCourier(agent) {
    const ref = documentRef( agent );
    const age = agent.parameters.age;
    return await ref.update({age: age});
  }

Note that I have also made getCourier() an async function, because the function calls that change the database (such as ref.update()) are async functions and Dialogflow requires you to either make it an async function or explicitly return a Promise. If you wish to return a Promise instead, this would be something more like this:

  function getCourier(agent) {
    const ref = documentRef( agent );
    const age = agent.parameters.age;
    return ref.update({age: age});
  }

Use the document name generated by Firestore

With this method, you'll store a document name as a Context parameter. When you go to save a value, you'll check if this document name is set. If it is, you'll do an update() using this document name. If not, you'll do an add(), get the document name, and save it in the Context parameter.

It might look something like this (untested), again for the age:

  async function getCourier( agent ){
    const ref = db.collection('users');
    const age = agent.parameters.age;

    const docContext = agent.context.get('doc');
    if( !docContext ){
      // We don't previously have a document, so create it
      const res = await ref.add({age:age});
      // And set a long-lasting context with a "name" parameter
      // containing the document id
      agent.context.set( 'doc', 99, {
        'name': ref.id
      } );
      
    } else {
      // There is a context with the document name already set
      // so get the name
      const docName = docContext.parameters['name'];
      const docRef = ref.doc(docName);
      // And save the data at this location
      await docRef.update({age: age});
    }
  }

Again, this uses an async function. If you'd rather use a Promise, it might be something more like this:

  function getCourier( agent ){
    const ref = db.collection('users');
    const age = agent.parameters.age;

    const docContext = agent.context.get('doc');
    if( !docContext ){
      // We don't previously have a document, so create it
      return ref.add({age:age})
        .then( ref => {
          // And set a long-lasting context with a "name" parameter
          // containing the document id
          agent.context.set( 'doc', 99, {
            'name': ref.id
          } );
        });
      
    } else {
      // There is a context with the document name already set
      // so get the name
      const docName = docContext.parameters['name'];
      const docRef = ref.doc(docName);
      // And save the data at this location
      return docRef.update({age: age});
    }
  }

Use a document name you've generated and saved in the context

You don't need to use the session id from the first alternative. If you have some ID or name that makes sense on your own (a username or a timestamp, for example, or some combination), then you can save this in a Context parameter and use this each time as the document name. This is a combination of the first and second approaches above (but probably simpler than the second one, since you don't need to get the document name from creating the document the fist time).

Upvotes: 1

Related Questions