Merge pull request #568 from codigoencasa/fix/fallback-issue

fix(bot):  fix fallback refactor
This commit is contained in:
Leifer Mendez
2023-01-28 15:47:03 +01:00
committed by GitHub
3 changed files with 204 additions and 60 deletions

118
__test__/05-case.test.js Normal file
View File

@@ -0,0 +1,118 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock')
const {
addKeyword,
createBot,
createFlow,
createProvider,
} = require('../packages/bot/index')
/**
* Falsear peticion async
* @param {*} fakeData
* @returns
*/
const fakeHTTP = async (fakeData = []) => {
await delay(5)
const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` }))
return Promise.resolve(data)
}
test(`[Caso - 05] Continuar Flujo (continueFlow)`, async () => {
const MOCK_VALUES = [
'¿CUal es tu email?',
'Continuamos....',
'¿Cual es tu edad?',
]
const provider = createProvider(PROVIDER_DB)
const database = new MOCK_DB()
const flujoPrincipal = addKeyword(['hola'])
.addAnswer(
MOCK_VALUES[0],
{
capture: true,
},
async (ctx, { flowDynamic, fallBack }) => {
const validation = ctx.body.includes('@')
if (validation) {
const getDataFromApi = await fakeHTTP([
'Gracias por tu email se ha validado de manera correcta',
])
return flowDynamic(getDataFromApi)
}
return fallBack(validation)
}
)
.addAnswer(MOCK_VALUES[1])
.addAnswer(
MOCK_VALUES[2],
{ capture: true },
async (ctx, { flowDynamic, fallBack }) => {
if (ctx.body !== '18') {
await delay(50)
return fallBack(false, 'Ups creo que no eres mayor de edad')
}
return flowDynamic('Bien tu edad es correcta!')
}
)
.addAnswer('Puedes pasar')
createBot({
database,
flow: createFlow([flujoPrincipal]),
provider,
})
provider.delaySendMessage(0, 'message', {
from: '000',
body: 'hola',
})
provider.delaySendMessage(10, 'message', {
from: '000',
body: 'this is not email value',
})
provider.delaySendMessage(20, 'message', {
from: '000',
body: 'test@test.com',
})
provider.delaySendMessage(90, 'message', {
from: '000',
body: '20',
})
provider.delaySendMessage(200, 'message', {
from: '000',
body: '18',
})
await delay(1200)
const getHistory = database.listHistory.map((i) => i.answer)
assert.is(MOCK_VALUES[0], getHistory[0])
assert.is('this is not email value', getHistory[1])
assert.is(MOCK_VALUES[0], getHistory[2])
assert.is('test@test.com', getHistory[3])
assert.is(
'1 Gracias por tu email se ha validado de manera correcta',
getHistory[4]
)
assert.is(MOCK_VALUES[1], getHistory[5])
assert.is(MOCK_VALUES[2], getHistory[6])
assert.is('20', getHistory[7])
assert.is('Ups creo que no eres mayor de edad', getHistory[8])
assert.is('18', getHistory[9])
assert.is('Bien tu edad es correcta!', getHistory[10])
assert.is('Puedes pasar', getHistory[11])
})
test.run()
function delay(ms) {
return new Promise((res) => setTimeout(res, ms))
}

View File

@@ -71,8 +71,8 @@ class CoreClass {
logger.log(`[handleMsg]: `, messageCtxInComming) logger.log(`[handleMsg]: `, messageCtxInComming)
const { body, from } = messageCtxInComming const { body, from } = messageCtxInComming
let msgToSend = [] let msgToSend = []
let fallBackFlag = false
let endFlowFlag = false let endFlowFlag = false
let fallBackFlag = false
if (this.generalArgs.blackList.includes(from)) return if (this.generalArgs.blackList.includes(from)) return
if (!body) return if (!body) return
if (!body.length) return if (!body.length) return
@@ -105,11 +105,23 @@ class CoreClass {
return return
} }
// 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx // 📄 Continuar con el siguiente flujo
const sendFlow = async (messageToSend, numberOrId) => { const continueFlow = async () => {
// [1 Paso] esto esta bien! const cotinueMessage =
this.flowClass.find(refToContinue?.ref, true) || []
sendFlow(cotinueMessage, from, { continue: true })
return
}
// 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx
const sendFlow = async (
messageToSend,
numberOrId,
options = { continue: false }
) => {
if (!options.continue && prevMsg?.options?.capture)
await cbEveryCtx(prevMsg?.ref)
if (prevMsg?.options?.capture) await cbEveryCtx(prevMsg?.ref)
const queue = [] const queue = []
for (const ctxMessage of messageToSend) { for (const ctxMessage of messageToSend) {
if (endFlowFlag) return if (endFlowFlag) return
@@ -127,11 +139,13 @@ class CoreClass {
} }
// 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje
const fallBack = async () => { const fallBack = async (next = false, message = null) => {
fallBackFlag = true
await this.sendProviderAndSave(from, refToContinue)
QueuePrincipal.queue = [] QueuePrincipal.queue = []
return refToContinue if (next) return continueFlow()
return this.sendProviderAndSave(from, {
...refToContinue,
answer: message ?? refToContinue.answer,
})
} }
// 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes // 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes
@@ -141,8 +155,7 @@ class CoreClass {
listMsg = [], listMsg = [],
optListMsg = { limit: 5, fallback: false } optListMsg = { limit: 5, fallback: false }
) => { ) => {
if (!Array.isArray(listMsg)) if (!Array.isArray(listMsg)) listMsg = [listMsg]
throw new Error('Esto debe ser un ARRAY')
fallBackFlag = optListMsg.fallback fallBackFlag = optListMsg.fallback
const parseListMsg = listMsg const parseListMsg = listMsg
@@ -165,7 +178,7 @@ class CoreClass {
for (const msg of parseListMsg) { for (const msg of parseListMsg) {
await this.sendProviderAndSave(from, msg) await this.sendProviderAndSave(from, msg)
} }
return return continueFlow()
} }
// 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback // 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback
@@ -181,11 +194,12 @@ class CoreClass {
fallBack, fallBack,
flowDynamic, flowDynamic,
endFlow, endFlow,
continueFlow,
}) })
} }
// 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa // 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa
if (!fallBackFlag && prevMsg?.options?.nested?.length) { if (prevMsg?.options?.nested?.length) {
const nestedRef = prevMsg.options.nested const nestedRef = prevMsg.options.nested
const flowStandalone = nestedRef.map((f) => ({ const flowStandalone = nestedRef.map((f) => ({
...nestedRef.find((r) => r.refSerialize === f.refSerialize), ...nestedRef.find((r) => r.refSerialize === f.refSerialize),
@@ -197,12 +211,11 @@ class CoreClass {
return return
} }
// 📄🤘(tiene return) [options: capture (boolean)]: Si se tiene option boolean // 📄🤘(tiene return) Si el mensaje previo implementa capture
if (!fallBackFlag && !prevMsg?.options?.nested?.length) { if (!prevMsg?.options?.nested?.length) {
const typeCapture = typeof prevMsg?.options?.capture const typeCapture = typeof prevMsg?.options?.capture
const valueCapture = prevMsg?.options?.capture
if (['string', 'boolean'].includes(typeCapture) && valueCapture) { if (typeCapture === 'boolean' && fallBackFlag) {
msgToSend = this.flowClass.find(refToContinue?.ref, true) || [] msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
sendFlow(msgToSend, from) sendFlow(msgToSend, from)
return return
@@ -228,6 +241,7 @@ class CoreClass {
} }
/** /**
* @deprecated
* @private * @private
* @param {*} message * @param {*} message
* @param {*} ref * @param {*} ref

View File

@@ -29,13 +29,16 @@ const flowPrincipal = addKeyword(['hola', 'alo'])
Es importante que el número **vaya acompañado de su prefijo**, en el caso de España "34". Es importante que el número **vaya acompañado de su prefijo**, en el caso de España "34".
```js ```js
createBot({ createBot(
{
flow: adapterFlow, flow: adapterFlow,
provider: adapterProvider, provider: adapterProvider,
database: adapterDB, database: adapterDB,
},{ },
blackList:['34XXXXXXXXX','34XXXXXXXXX','34XXXXXXXXX','34XXXXXXXXX'] {
}) blackList: ['34XXXXXXXXX', '34XXXXXXXXX', '34XXXXXXXXX', '34XXXXXXXXX'],
}
)
``` ```
--- ---
@@ -175,65 +178,74 @@ const flowString = addKeyword('hola')
``` ```
--- ---
## endFlow() ## endFlow()
Esta funcion se utliza para finalizar un flujo con dos o más addAnswer. Un ejemplo de uso sería registrar 3 datos de un usuario en 3 preguntas distinas y Esta funcion se utliza para finalizar un flujo con dos o más addAnswer. Un ejemplo de uso sería registrar 3 datos de un usuario en 3 preguntas distinas y
que el usuario pueda finalizar por él mismo el flujo. que el usuario pueda finalizar por él mismo el flujo.
Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones. Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones.
```js ```js
const flowFormulario = addKeyword(['Hola']) const flowFormulario = addKeyword(['Hola'])
.addAnswer(
.addAnswer(['Hola!','Escriba su *Nombre* para generar su solicitud'], ['Hola!', 'Escriba su *Nombre* para generar su solicitud'],
{capture: true,buttons:[{body:'❌ Cancelar solicitud'}]}, { capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
async (ctx,{flowDynamic, endFlow})=>{ async (ctx, { flowDynamic, endFlow }) => {
if(ctx.body == '❌ Cancelar solicitud'){ if (ctx.body == '❌ Cancelar solicitud') {
await flowDynamic([{body: "❌ *Su solicitud de cita ha sido cancelada* ❌", buttons:[{body:'⬅️ Volver al Inicio'}]}]) await flowDynamic([
return endFlow() {
} body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
}) buttons: [{ body: '⬅️ Volver al Inicio' }],
.addAnswer(['También necesito tus dos apellidos'], },
{capture: true,buttons:[{body:'❌ Cancelar solicitud'}]}, ])
async (ctx,{flowDynamic, endFlow})=>{ return endFlow()
if(ctx.body == '❌ Cancelar solicitud'){ }
await flowDynamic([{body: "❌ *Su solicitud de cita ha sido cancelada* ❌", buttons:[{body:'⬅️ Volver al Inicio'}]}]) }
return endFlow() )
} .addAnswer(
}) ['También necesito tus dos apellidos'],
.addAnswer(['Dejeme su número de teléfono y le llamaré lo antes posible.'], { capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
{capture: true,buttons:[{body:'❌ Cancelar solicitud'}]}, async (ctx, { flowDynamic, endFlow }) => {
async (ctx,{flowDynamic, endFlow})=>{ if (ctx.body == '❌ Cancelar solicitud') {
if(ctx.body == '❌ Cancelar solicitud'){ await flowDynamic([
await flowDynamic([{body: "❌ *Su solicitud de cita ha sido cancelada* ❌", buttons:[{body:'⬅️ Volver al Inicio'}]}]) {
return endFlow() body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
} buttons: [{ body: '⬅️ Volver al Inicio' }],
}) },
])
return endFlow()
}
}
)
.addAnswer(
['Dejeme su número de teléfono y le llamaré lo antes posible.'],
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] },
async (ctx, { flowDynamic, endFlow }) => {
if (ctx.body == '❌ Cancelar solicitud') {
await flowDynamic([
{
body: '❌ *Su solicitud de cita ha sido cancelada* ❌',
buttons: [{ body: '⬅️ Volver al Inicio' }],
},
])
return endFlow()
}
}
)
``` ```
--- ---
# QRPortalWeb # QRPortalWeb
Argumento para asignar nombre y puerto al BOT Argumento para asignar nombre y puerto al BOT
```js ```js
QRPortalWeb({name:BOTNAME, port:3005 }); QRPortalWeb({ name: BOTNAME, port: 3005 })
``` ```
--- ---
<Navigation <Navigation
pages={[ pages={[
{ name: 'Conceptos', link: '/docs/essential' }, { name: 'Conceptos', link: '/docs/essential' },