Skip to content

Commit

Permalink
[3] Move selectedArticleId from context to action payload (#369)
Browse files Browse the repository at this point in the history
Fixes #327 

- Removes `selectedArticleId` from context
    - We embed `selectedArticleId` to payload action instead
- `choosingReply` defines the payload input type and loads `selectedArticleId` from postback payload
  • Loading branch information
MrOrz authored Nov 22, 2023
2 parents 90358bc + bf77c4d commit 626b5af
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 31 deletions.
2 changes: 0 additions & 2 deletions src/types/chatbotState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export type ChatbotState =
export type Context = {
/** Used to differientiate different search sessions (searched text or media) */
sessionId: number;
/** User selected article in DB */
selectedArticleId?: string;
} & (
| {
/** Searched multi-media message that started this search session */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ exports[`should select article and choose the only one reply for user 1`] = `
Object {
"data": Object {
"searchedText": "Just One Reply Just One Reply Just One Reply Just One Reply Just One Reply",
"selectedArticleId": "article-id",
"sessionId": 0,
},
"replies": Array [
Expand Down Expand Up @@ -328,7 +327,6 @@ exports[`should select article and have OPINIONATED and NOT_ARTICLE replies 1`]
Object {
"data": Object {
"searchedText": "老榮民九成存款全部捐給慈濟,如今窮了卻得不到慈濟醫院社工的幫忙,竟翻臉不認人",
"selectedArticleId": "article-id",
"sessionId": 1497994017447,
},
"replies": Array [
Expand Down Expand Up @@ -675,7 +673,6 @@ exports[`should select article and slice replies when over 10 1`] = `
Object {
"data": Object {
"searchedText": "老榮民九成存款全部捐給慈濟,如今窮了卻得不到慈濟醫院社工的幫忙,竟翻臉不認人",
"selectedArticleId": "article-id",
"sessionId": 0,
},
"replies": Array [
Expand Down Expand Up @@ -1500,7 +1497,6 @@ Object {
因為最近已經發現- 好多病人因為吃了生魚片,胃壁附著《海獸胃腺蟲》,大小隻不一定,有的病人甚至胃壁上滿滿都是無法夾出來,驅蟲藥也很難根治,罹患機率每個國家的人都一樣。
尤其;鮭魚的含蟲量最高、最可怕!
請傳給朋友,讓他們有所警惕!",
"selectedArticleId": "article-id",
"sessionId": 0,
},
"replies": Array [
Expand Down Expand Up @@ -1689,7 +1685,6 @@ exports[`should select article with no replies: has AI reply 1`] = `
Object {
"data": Object {
"searchedText": "老司機車裡總備一塊香皂,知道內情的新手默默也準備了一塊",
"selectedArticleId": "article-id",
"sessionId": 0,
},
"replies": Array [
Expand Down Expand Up @@ -1869,7 +1864,6 @@ exports[`should select article with no replies: has no AI reply 1`] = `
Object {
"data": Object {
"searchedText": "老司機車裡總備一塊香皂,知道內情的新手默默也準備了一塊",
"selectedArticleId": "article-id",
"sessionId": 0,
},
"replies": Array [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ exports[`should select reply by replyId should handle the case with just one rep
Object {
"data": Object {
"searchedText": "貼圖",
"selectedArticleId": "AWDZYXxAyCdS-nWhumlz",
"sessionId": 0,
},
"replies": Array [
Expand Down Expand Up @@ -373,7 +372,6 @@ exports[`should select reply by replyId should handle the case with multiple rep
Object {
"data": Object {
"searchedText": "貼圖",
"selectedArticleId": "AWDZYXxAyCdS-nWhumlz",
"sessionId": 0,
},
"replies": Array [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,6 @@ exports[`only one article found with high similarity and choose for user 1`] = `
Object {
"data": Object {
"searchedText": "YouTube · 寻找健康人生",
"selectedArticleId": "AVvY-yizyCdS-nWhuYWx",
"sessionId": 1497994017447,
},
"replies": Array [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ Object {
"messageId": "6270464463537",
"messageType": "image",
"searchedText": "",
"selectedArticleId": "image-article-1",
"sessionId": 1577836800000,
},
},
Expand Down
20 changes: 13 additions & 7 deletions src/webhook/handlers/__tests__/choosingReply.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ jest.mock('src/lib/gql');
jest.mock('src/lib/ga');

import MockDate from 'mockdate';
import choosingReply from '../choosingReply';
import choosingReply, { Input } from '../choosingReply';
import * as apiResult from '../__fixtures__/choosingReply';
import UserSettings from 'src/database/models/userSettings';
import originalGql from 'src/lib/gql';
Expand All @@ -25,15 +25,19 @@ afterEach(() => {
});

describe('should select reply by replyId', () => {
const input: Input = {
a: 'AWDZYXxAyCdS-nWhumlz',
r: 'AWDZeeV0yCdS-nWhuml8',
};

const params: ChatbotPostbackHandlerParams = {
data: {
sessionId: 0,
searchedText: '貼圖',
selectedArticleId: 'AWDZYXxAyCdS-nWhumlz',
},
postbackData: {
sessionId: 0,
input: 'AWDZeeV0yCdS-nWhuml8',
input,
state: 'CHOOSING_REPLY',
},
userId: 'Uaddc74df8a3a176b901d9d648b0fc4fe',
Expand Down Expand Up @@ -100,12 +104,11 @@ it('should block invalid postback input', async () => {
data: {
sessionId: 0,
searchedText: '貼圖',
selectedArticleId: 'AWDZYXxAyCdS-nWhumlz',
},
postbackData: {
sessionId: 0,
state: 'CHOOSING_REPLY',
input: undefined,
input: 'Some wrong string',
},
userId: 'Uaddc74df8a3a176b901d9d648b0fc4fe',
};
Expand All @@ -117,16 +120,19 @@ it('should block invalid postback input', async () => {

it('should handle graphql error gracefully', async () => {
gql.__push({ errors: [] });
const input: Input = {
a: 'AWDZYXxAyCdS-nWhumlz',
r: 'AWDZeeV0yCdS-nWhuml8',
};

const params: ChatbotPostbackHandlerParams = {
data: {
sessionId: 0,
searchedText: '貼圖',
selectedArticleId: 'AWDZYXxAyCdS-nWhumlz',
},
postbackData: {
sessionId: 0,
input: 'AWDZeeV0yCdS-nWhuml8',
input,
state: 'CHOOSING_REPLY',
},
userId: 'Uaddc74df8a3a176b901d9d648b0fc4fe',
Expand Down
10 changes: 8 additions & 2 deletions src/webhook/handlers/choosingArticle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {

import UserArticleLink from '../../database/models/userArticleLink';
import choosingReply from './choosingReply';
import type { Input as ChoosingReplyInput } from './choosingReply';
import { ChatbotPostbackHandler } from 'src/types/chatbotState';
import { FlexBubble, Message } from '@line/bot-sdk';

Expand Down Expand Up @@ -116,7 +117,7 @@ const choosingArticle: ChatbotPostbackHandler = async (params) => {
};
}

const selectedArticleId = (data.selectedArticleId = input);
const selectedArticleId = input;

await UserArticleLink.createOrUpdateByUserIdAndArticleId(
userId,
Expand Down Expand Up @@ -171,13 +172,18 @@ const choosingArticle: ChatbotPostbackHandler = async (params) => {
if (articleReplies.length === 1) {
visitor.send();

const input: ChoosingReplyInput = {
a: selectedArticleId,
r: articleReplies[0].reply?.id ?? '',
};

// choose reply for user
return await choosingReply({
data,
postbackData: {
sessionId,
state: 'CHOOSING_REPLY',
input: articleReplies[0].reply?.id ?? '',
input,
},
userId,
});
Expand Down
30 changes: 20 additions & 10 deletions src/webhook/handlers/choosingReply.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { t } from 'ttag';
import { z } from 'zod';
import gql from 'src/lib/gql';
import {
ManipulationError,
Expand All @@ -17,6 +18,14 @@ import {
ReplyTypeEnum,
} from 'typegen/graphql';

const inputSchema = z.object({
a: z.string().describe('Article ID'),
r: z.string().describe('Reply ID'),
});

/** Postback input type for CHOOSING_REPLY state handler */
export type Input = z.infer<typeof inputSchema>;

/**
* @param {string} articleId - Article ID of the article-reply to feedback
* @param {string} replyId - Reply ID of the article-reply to feedback
Expand Down Expand Up @@ -133,13 +142,17 @@ function createShareBubble(
const choosingReply: ChatbotPostbackHandler = async ({
data,
userId,
postbackData: { input, state },
postbackData: { input: postbackInput, state },
}) => {
if (typeof input !== 'string') {
let input: Input;
try {
input = inputSchema.parse(postbackInput);
} catch (e) {
console.error('[choosingReply]', e);
throw new ManipulationError(t`Please choose from provided options.`);
}

const selectedReplyId = input;
const { a: selectedArticleId, r: selectedReplyId } = input;

const { data: getReplyData, errors } = await gql`
query GetReplyRelatedData($id: String!, $articleId: String!) {
Expand All @@ -156,7 +169,7 @@ const choosingReply: ChatbotPostbackHandler = async ({
}
`<GetReplyRelatedDataQuery, GetReplyRelatedDataQueryVariables>({
id: selectedReplyId,
articleId: data.selectedArticleId ?? '',
articleId: selectedArticleId,
});

/* istanbul ignore if */
Expand All @@ -177,25 +190,22 @@ const choosingReply: ChatbotPostbackHandler = async ({
);

const replies: Message[] = [
...createReplyMessages(GetReply, GetArticle, data.selectedArticleId ?? ''),
...createReplyMessages(GetReply, GetArticle, selectedArticleId),
{
type: 'flex',
altText: t`Is the reply helpful?`,
contents: {
type: 'carousel',
contents: [
createAskReplyFeedbackBubble(
data.selectedArticleId ?? '',
selectedReplyId
),
createAskReplyFeedbackBubble(selectedArticleId, selectedReplyId),

// Ask user to turn on notification if the user did not turn it on
process.env.NOTIFY_METHOD &&
!allowNewReplyUpdate &&
createNotificationSettingsBubble(),

createShareBubble(
data.selectedArticleId ?? '',
selectedArticleId,
getReplyData.GetArticle.text ?? '',
GetReply.type
),
Expand Down

0 comments on commit 626b5af

Please sign in to comment.