app-tutor-ai-consumer 1.31.1 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # [1.32.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.31.1...v1.32.0) (2025-10-03)
2
+
3
+ ### Features
4
+
5
+ - add messages datahub events ([5d271e5](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/5d271e594162e665d0b10fba17b26b8b92f09325))
6
+
1
7
  ## [1.31.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.31.0...v1.31.1) (2025-10-01)
2
8
 
3
9
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.31.1",
3
+ "version": "1.32.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
@@ -13,6 +13,7 @@ export const ActionNames = {
13
13
  CLICK_HOTMART_TUTOR: `${DataHubActions.CLICK}_hotmart_tutor`,
14
14
  CLICK_TUTOR_MINIMIZE: `${DataHubActions.CLICK}_tutor_minimize`,
15
15
  TRY_PRODUCT_TUTOR: `${DataHubActions.TRY}_product_tutor`,
16
+ TRY_SEND_TUTOR_MESSAGE: `${DataHubActions.TRY}_send_tutor_message`,
16
17
  CLICK_TUTOR_BACK: `${DataHubActions.CLICK}_tutor_back`,
17
18
  CLICK_TUTOR_HISTORY: `${DataHubActions.CLICK}_tutor_history`,
18
19
  CLICK_TUTOR_INFO: `${DataHubActions.CLICK}_tutor_info`,
@@ -20,5 +21,6 @@ export const ActionNames = {
20
21
  CLICK_TUTOR_ONBOARDING_START: `${DataHubActions.CLICK}_tutor_onboarding_start`,
21
22
  CLICK_TUTOR_TEST_KNOWLEDGE: `${DataHubActions.CLICK}_tutor_test_knowledge`,
22
23
  CLOSE_TUTOR_ONBOARDING: `${DataHubActions.CLOSE}_tutor_onboarding`,
23
- VIEW_PRODUCT_TUTOR: `${DataHubActions.VIEW}_product_tutor`
24
+ VIEW_PRODUCT_TUTOR: `${DataHubActions.VIEW}_product_tutor`,
25
+ VIEW_TUTOR_ANSWER_MESSAGE: `${DataHubActions.VIEW}_tutor_answer_message`
24
26
  }
@@ -27,6 +27,8 @@ export const ComponentNames = {
27
27
  BUTTON_COPY_ANSWER: 'BUTTON_COPY_ANSWER',
28
28
  BUTTON_CLOSE_TUTOR_CHAT: 'BUTTON_CLOSE_TUTOR_CHAT',
29
29
  BUTTON_VIEW_AGENT: 'BUTTON_VIEW_AGENT',
30
+ BUTTON_SEND_MESSAGE: 'BUTTON_SEND_MESSAGE',
31
+ VIEW_ANSWER: 'VIEW_ANSWER',
30
32
  PRODUCT_CONSUME_CLICK_TUTOR_BACK: 'product_consume_click_tutor_back',
31
33
  PRODUCT_CONSUME_CLICK_TUTOR_HISTORY: 'product_consume_click_tutor_history',
32
34
  PRODUCT_CONSUME_CLICK_TUTOR_INFO: 'product_consume_click_tutor_info',
@@ -0,0 +1,38 @@
1
+ import { chance } from '@/src/config/tests'
2
+ import { HttpCodes } from '@/src/lib/utils'
3
+ import { ComponentNames, ComponentSource } from '../../../constants'
4
+ import { DataHubEntities } from '../../../entities'
5
+ import TrySendTutorMessage from '../try-send-tutor-message'
6
+
7
+ describe('TrySendTutorMessage', () => {
8
+ it('should send the event with the correct structure', () => {
9
+ const event = {
10
+ questionType: 'TEXT',
11
+ questionId: chance.guid()
12
+ }
13
+
14
+ expect(new TrySendTutorMessage(event).getDataHubEventData()).toMatchObject({
15
+ appVersion: '',
16
+ clubVersion: 'MEMBERSHIP',
17
+ componentName: ComponentNames.BUTTON_SEND_MESSAGE,
18
+ componentSource: ComponentSource.HOTMART_TUTOR,
19
+ entity: DataHubEntities.PRODUCT_CONSUME,
20
+ failureDescription: 'NOT_FAILURE_RESULT_EVENT',
21
+ isLogged: true,
22
+ membershipId: 'UNDEFINED',
23
+ membershipSlug: 'UNDEFINED',
24
+ osVersion: '537.36',
25
+ platform: 'WEB',
26
+ platformDetail: 'WebKit',
27
+ questionId: event.questionId,
28
+ questionType: event.questionType,
29
+ result: 'SUCCESSFUL',
30
+ sessionId: 'UNDEFINED',
31
+ statusCode: HttpCodes.OK,
32
+ ucode: 'UNDEFINED',
33
+ url: 'http://localhost:3000/',
34
+ userId: 0,
35
+ userRole: 'CONSUMER'
36
+ })
37
+ })
38
+ })
@@ -0,0 +1,39 @@
1
+ import { chance } from '@/src/config/tests'
2
+ import { ComponentNames, ComponentSource } from '../../../constants'
3
+ import { DataHubEntities } from '../../../entities'
4
+ import { UserRole } from '../../constants'
5
+ import ViewTutorAnswerMessage from '../view-tutor-answer-message'
6
+
7
+ describe('ViewTutorAnswerMessage', () => {
8
+ it('should send the event with the correct structure', () => {
9
+ const event = {
10
+ messageType: 'TEXT',
11
+ messageId: chance.guid(),
12
+ correlationId: chance.guid()
13
+ }
14
+
15
+ expect(new ViewTutorAnswerMessage(event).getDataHubEventData()).toMatchObject({
16
+ appVersion: '',
17
+ arrivedFrom: 'HOME_CLASS_CONSUMER',
18
+ clubVersion: 'MEMBERSHIP',
19
+ componentName: ComponentNames.VIEW_ANSWER,
20
+ componentSource: ComponentSource.HOTMART_TUTOR,
21
+ correlationId: event.correlationId,
22
+ entity: DataHubEntities.PRODUCT_CONSUME,
23
+ isLogged: true,
24
+ membershipId: 'UNDEFINED',
25
+ membershipSlug: 'UNDEFINED',
26
+ messageId: event.messageId,
27
+ messageType: event.messageType,
28
+ osVersion: '537.36',
29
+ platform: 'WEB',
30
+ platformDetail: 'WebKit',
31
+ productType: 'category.online_services.name',
32
+ sessionId: 'UNDEFINED',
33
+ ucode: 'UNDEFINED',
34
+ url: 'http://localhost:3000/',
35
+ userId: 0,
36
+ userRole: UserRole.CONSUMER
37
+ })
38
+ })
39
+ })
@@ -8,4 +8,6 @@ export { default as ClickTutorOnboardingStartSchema } from './click-tutor-onboar
8
8
  export { default as ClickTutorTestKnowledgeSchema } from './click-tutor-test-knowledge'
9
9
  export { default as CloseTutorOnboardingSchema } from './close-tutor-onboarding'
10
10
  export * from './constants'
11
+ export { default as TrySendTutorMessageSchema } from './try-send-tutor-message'
11
12
  export * from './types'
13
+ export { default as ViewTutorAnswerMessageSchema } from './view-tutor-answer-message'
@@ -0,0 +1,55 @@
1
+ import { HttpCodes } from '@/src/lib/utils'
2
+ import { ActionNames } from '../../actions'
3
+ import { ComponentNames, ComponentSource } from '../../constants'
4
+ import { DataHubEntities } from '../../entities'
5
+ import type { DataHubEntityTypes } from '../../types'
6
+ import type { ActionNamesType } from '../../types'
7
+ import type { BaseTrySchemaConstructorArgs } from '../base-try-schema'
8
+ import BaseTrySchema from '../base-try-schema'
9
+
10
+ export type TrySendTutorMessageConstructorArgs = Partial<BaseTrySchemaConstructorArgs> & {
11
+ questionType?: string
12
+ questionId?: string
13
+ }
14
+
15
+ class TrySendTutorMessageSchema extends BaseTrySchema {
16
+ action: ActionNamesType
17
+ entity: DataHubEntityTypes
18
+ questionType: string
19
+ questionId: string
20
+
21
+ constructor(args?: TrySendTutorMessageConstructorArgs) {
22
+ const {
23
+ questionType = 'TEXT',
24
+ questionId = '',
25
+ componentSource = ComponentSource.HOTMART_TUTOR,
26
+ componentName = ComponentNames.BUTTON_SEND_MESSAGE,
27
+ failureDescription = 'NOT_FAILURE_RESULT_EVENT',
28
+ result = 'SUCCESSFUL',
29
+ statusCode = HttpCodes.OK
30
+ } = args ?? {}
31
+
32
+ super({
33
+ componentName,
34
+ componentSource,
35
+ failureDescription,
36
+ result,
37
+ statusCode
38
+ })
39
+
40
+ this.action = ActionNames.TRY_SEND_TUTOR_MESSAGE
41
+ this.entity = DataHubEntities.PRODUCT_CONSUME
42
+ this.questionType = questionType
43
+ this.questionId = questionId
44
+ }
45
+
46
+ getDataHubEventData(): Record<string, unknown> {
47
+ return {
48
+ ...this.prepare(),
49
+ questionType: this.questionType,
50
+ questionId: this.questionId
51
+ }
52
+ }
53
+ }
54
+
55
+ export default TrySendTutorMessageSchema
@@ -0,0 +1,58 @@
1
+ import { ActionNames } from '../../actions'
2
+ import { ComponentNames, ComponentSource } from '../../constants'
3
+ import type { ActionNamesType } from '../../types'
4
+ import type { BaseViewSchemaConstructorArgs } from '../base-view-schema'
5
+ import BaseViewSchema from '../base-view-schema'
6
+
7
+ export type ViewTutorAnswerMessageConstructorArgs = Partial<BaseViewSchemaConstructorArgs> & {
8
+ messageType?: string
9
+ messageId?: string
10
+ correlationId?: string
11
+ }
12
+
13
+ class ViewTutorAnswerMessageSchema extends BaseViewSchema {
14
+ private messageType?: string
15
+ private messageId?: string
16
+ private correlationId?: string
17
+
18
+ action: ActionNamesType
19
+
20
+ constructor(args?: ViewTutorAnswerMessageConstructorArgs) {
21
+ const {
22
+ messageType = 'TEXT',
23
+ messageId,
24
+ correlationId,
25
+ arrivedFrom,
26
+ entity,
27
+ isLogged,
28
+ productType,
29
+ componentName = ComponentNames.VIEW_ANSWER,
30
+ componentSource = ComponentSource.HOTMART_TUTOR
31
+ } = args ?? {}
32
+
33
+ super({
34
+ arrivedFrom,
35
+ entity,
36
+ isLogged,
37
+ productType,
38
+ componentName,
39
+ componentSource
40
+ })
41
+
42
+ this.action = ActionNames.VIEW_TUTOR_ANSWER_MESSAGE
43
+ this.messageType = messageType
44
+ this.messageId = messageId
45
+ this.correlationId = correlationId
46
+ }
47
+
48
+ getDataHubEventData(): Record<string, unknown> {
49
+ return {
50
+ ...super.prepare(),
51
+ messageType: this.messageType,
52
+ messageId: this.messageId,
53
+ correlationId: this.correlationId
54
+ }
55
+ }
56
+ }
57
+
58
+ export default ViewTutorAnswerMessageSchema
@@ -1,5 +1,6 @@
1
1
  import { UAParser } from 'ua-parser-js'
2
2
 
3
+ import { DataHubService } from '@/src/config/datahub'
3
4
  import { act, chance, renderHook, waitFor } from '@/src/config/tests'
4
5
  import { APP_VERSION, PLATFORM } from '@/src/lib/utils'
5
6
  import { DirectMessagesService as MessagesService } from '@/src/modules/messages'
@@ -14,6 +15,12 @@ vi.mock('@/src/modules/profile', () => ({
14
15
  useGetProfile: vi.fn()
15
16
  }))
16
17
 
18
+ vi.mock('@/src/modules/widget', () => ({
19
+ useWidgetSettingsAtomValue: vi.fn(),
20
+ useWidgetLoadingAtom: vi.fn(() => [false, vi.fn()]),
21
+ useIsAgentParentAtomValue: vi.fn(() => false)
22
+ }))
23
+
17
24
  vi.mock('ua-parser-js', () => ({ UAParser: vi.fn() }))
18
25
 
19
26
  describe('useSendTextMessage', () => {
@@ -24,7 +31,10 @@ describe('useSendTextMessage', () => {
24
31
  .withOwner_id(chance.guid())
25
32
  .withCurrent_media_codes(chance.profession())
26
33
  .withUser({ id: chance.guid(), ucode: chance.guid() })
27
- const UAParserMock = { browser: { version: chance.android_id(), name: chance.name_prefix() } }
34
+ const UAParserMock = {
35
+ browser: { version: chance.android_id(), name: chance.name_prefix() },
36
+ device: { type: 'desktop', vendor: 'test', model: 'test' }
37
+ }
28
38
 
29
39
  const render = () => renderHook(useSendTextMessage)
30
40
 
@@ -32,6 +42,7 @@ describe('useSendTextMessage', () => {
32
42
  vi.mocked(useGetProfile).mockReturnValue(getProfileMock as never)
33
43
  vi.spyOn(Store, 'useWidgetSettingsAtomValue').mockReturnValue(defaultSettings)
34
44
  vi.mocked(UAParser).mockReturnValue(UAParserMock as never)
45
+ vi.mocked(Store.useIsAgentParentAtomValue).mockReturnValue(false)
35
46
  })
36
47
 
37
48
  it('should throw when conversationId is not defined', async () => {
@@ -54,6 +65,23 @@ describe('useSendTextMessage', () => {
54
65
  expect(result.current.isSuccess).toBeTruthy()
55
66
  })
56
67
 
68
+ it('should send DataHub event on successful message send', async () => {
69
+ const mockResponse = { id: chance.guid() }
70
+ vi.spyOn(MessagesService, 'create').mockResolvedValue(mockResponse as never)
71
+ vi.spyOn(DataHubService, 'sendEvent')
72
+
73
+ const { result } = render()
74
+
75
+ await waitFor(() => result.current.mutateAsync(message))
76
+
77
+ expect(DataHubService.sendEvent).toHaveBeenCalledWith({
78
+ schema: expect.objectContaining({
79
+ questionId: mockResponse.id,
80
+ questionType: 'TEXT'
81
+ })
82
+ })
83
+ })
84
+
57
85
  it('should be properly able to handle question::<text> messages', async () => {
58
86
  const txt = 'question::summary'
59
87
  const text = txt.replace('question::', '')
@@ -3,10 +3,16 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'
3
3
  import { UAParser } from 'ua-parser-js'
4
4
  import { v4 } from 'uuid'
5
5
 
6
- import { APP_VERSION, PLATFORM } from '@/src/lib/utils'
6
+ import { ComponentSource, DataHubService } from '@/src/config/datahub'
7
+ import { TrySendTutorMessageSchema } from '@/src/config/datahub/schemas/tutor'
8
+ import { APP_VERSION, HttpCodes, PLATFORM } from '@/src/lib/utils'
7
9
  import { DirectMessagesService as MessagesService } from '@/src/modules/messages'
8
10
  import { useGetProfile } from '@/src/modules/profile'
9
- import { useWidgetLoadingAtom, useWidgetSettingsAtomValue } from '@/src/modules/widget'
11
+ import {
12
+ useIsAgentParentAtomValue,
13
+ useWidgetLoadingAtom,
14
+ useWidgetSettingsAtomValue
15
+ } from '@/src/modules/widget'
10
16
  import { MessagesEvents } from '../../events'
11
17
  import { useMessagesMaxCount } from '../../store'
12
18
  import { setMessagesCache } from '../../utils'
@@ -18,6 +24,7 @@ function useSendTextMessage() {
18
24
  const [, setWidgetLoading] = useWidgetLoadingAtom()
19
25
  const queryClient = useQueryClient()
20
26
  const limit = useMessagesMaxCount()
27
+ const isAgentMode = useIsAgentParentAtomValue()
21
28
 
22
29
  const userId = useMemo(() => profileQuery.data?.userId?.toString(), [profileQuery.data?.userId])
23
30
  const profileId = useMemo(() => profileQuery.data?.id?.toString(), [profileQuery.data?.id])
@@ -108,6 +115,27 @@ function useSendTextMessage() {
108
115
  },
109
116
  onSuccess(data) {
110
117
  setMessagesCache({ queryKey: messagesQueryConfig.queryKey, queryClient, data })()
118
+
119
+ DataHubService.sendEvent({
120
+ schema: new TrySendTutorMessageSchema({
121
+ questionId: data.id,
122
+ componentSource: isAgentMode
123
+ ? ComponentSource.PRODUCT_AGENT
124
+ : ComponentSource.HOTMART_TUTOR
125
+ })
126
+ })
127
+ },
128
+ onError(error) {
129
+ DataHubService.sendEvent({
130
+ schema: new TrySendTutorMessageSchema({
131
+ statusCode: HttpCodes.BAD_REQUEST,
132
+ result: 'FAILURE',
133
+ failureDescription: error.message,
134
+ componentSource: isAgentMode
135
+ ? ComponentSource.PRODUCT_AGENT
136
+ : ComponentSource.HOTMART_TUTOR
137
+ })
138
+ })
111
139
  }
112
140
  })
113
141
  }
@@ -3,10 +3,16 @@ import type { InfiniteData } from '@tanstack/react-query'
3
3
  import { useQueryClient } from '@tanstack/react-query'
4
4
  import { produce } from 'immer'
5
5
 
6
+ import { ComponentSource, DataHubService } from '@/src/config/datahub'
7
+ import { ViewTutorAnswerMessageSchema } from '@/src/config/datahub/schemas/tutor'
6
8
  import { useUpdateCursor } from '@/src/modules/cursor/hooks'
7
9
  import { useGetProfile } from '@/src/modules/profile'
8
10
  import { SparkieService } from '@/src/modules/sparkie'
9
- import { useWidgetLoadingAtom, useWidgetSettingsAtom } from '@/src/modules/widget'
11
+ import {
12
+ useIsAgentParentAtomValue,
13
+ useWidgetLoadingAtom,
14
+ useWidgetSettingsAtom
15
+ } from '@/src/modules/widget'
10
16
  import { useMessagesMaxCount, useUnreadMessagesSetAtom } from '../../store'
11
17
  import type { FetchMessagesResponse, IMessageWithSenderData } from '../../types'
12
18
  import { getAllMessagesQuery } from '../use-infinite-get-messages'
@@ -19,6 +25,7 @@ const useSubscribeMessageReceivedEvent = () => {
19
25
  const queryClient = useQueryClient()
20
26
  const useUpdateCursorMutation = useUpdateCursor()
21
27
  const limit = useMessagesMaxCount()
28
+ const isAgentMode = useIsAgentParentAtomValue()
22
29
 
23
30
  const conversationId = useMemo(() => String(settings?.conversationId), [settings?.conversationId])
24
31
  const profileId = useMemo(() => String(profileQuery?.data?.id), [profileQuery?.data?.id])
@@ -59,6 +66,16 @@ const useSubscribeMessageReceivedEvent = () => {
59
66
  const isMine = data.contactId === profileId
60
67
 
61
68
  if (!isMine) {
69
+ DataHubService.sendEvent({
70
+ schema: new ViewTutorAnswerMessageSchema({
71
+ correlationId: data.metadata.correlationId,
72
+ messageId: data.id,
73
+ componentSource: isAgentMode
74
+ ? ComponentSource.PRODUCT_AGENT
75
+ : ComponentSource.HOTMART_TUTOR
76
+ })
77
+ })
78
+
62
79
  addUnreadMessagesToSet({ itemId: data.id })
63
80
  setTimeout(() => setWidgetLoading(false), 100)
64
81
  } else {
@@ -69,6 +86,7 @@ const useSubscribeMessageReceivedEvent = () => {
69
86
  [
70
87
  addUnreadMessagesToSet,
71
88
  conversationId,
89
+ isAgentMode,
72
90
  messagesQueryConfig.queryKey,
73
91
  profileId,
74
92
  queryClient,