diff --git a/packages/headless/doc-parser/use-cases/insight.ts b/packages/headless/doc-parser/use-cases/insight.ts index 223819442a9..6ef937befe3 100644 --- a/packages/headless/doc-parser/use-cases/insight.ts +++ b/packages/headless/doc-parser/use-cases/insight.ts @@ -157,6 +157,9 @@ const actionLoaders: ActionLoaderConfiguration[] = [ { initializer: 'loadGenericAnalyticsActions', }, + { + initializer: 'loadInsightUserActionsActions', + }, ]; const engine: EngineConfiguration = { diff --git a/packages/headless/src/controllers/insight/user-actions/headless-user-actions.test.ts b/packages/headless/src/controllers/insight/user-actions/headless-user-actions.test.ts index cbe582e71db..6ad8cdcce66 100644 --- a/packages/headless/src/controllers/insight/user-actions/headless-user-actions.test.ts +++ b/packages/headless/src/controllers/insight/user-actions/headless-user-actions.test.ts @@ -1,4 +1,5 @@ import {configuration} from '../../../app/common-reducers'; +import {logOpenUserActions} from '../../../features/insight-search/insight-analytics-actions'; import { fetchUserActions, registerUserActions, @@ -19,6 +20,8 @@ jest.mock( '../../../features/insight-user-actions/insight-user-actions-actions' ); +jest.mock('../../../features/insight-search/insight-analytics-actions'); + describe('UserActions', () => { let engine: MockedInsightEngine; @@ -71,4 +74,9 @@ describe('UserActions', () => { expect(fetchUserActions).toHaveBeenCalled(); expect(fetchUserActions).toHaveBeenCalledWith(exampleUserId); }); + + it('#logOpenUserActions dispatches #logOpenUserActions', () => { + userActions.logOpenUserActions(); + expect(logOpenUserActions).toHaveBeenCalled(); + }); }); diff --git a/packages/headless/src/controllers/insight/user-actions/headless-user-actions.ts b/packages/headless/src/controllers/insight/user-actions/headless-user-actions.ts index d0532f8aca7..244a890ceed 100644 --- a/packages/headless/src/controllers/insight/user-actions/headless-user-actions.ts +++ b/packages/headless/src/controllers/insight/user-actions/headless-user-actions.ts @@ -1,5 +1,6 @@ import {configuration} from '../../../app/common-reducers'; import {InsightEngine} from '../../../app/insight-engine/insight-engine'; +import {logOpenUserActions} from '../../../features/insight-search/insight-analytics-actions'; import { fetchUserActions, registerUserActions, @@ -16,8 +17,8 @@ import { Controller, } from '../../controller/headless-controller'; -export type {UserActionsState} from '../../../features/insight-user-actions/insight-user-actions-state'; export type { + UserActionsState, UserAction, UserSession, } from '../../../features/insight-user-actions/insight-user-actions-state'; @@ -50,6 +51,10 @@ export interface UserActions extends Controller { * @param userId The user ID to which the user's actions belong. */ fetchUserActions(userId: string): void; + /** + * Emits an analytics event indicating that the user actions panel was opened. + */ + logOpenUserActions(): void; /** * The state of the UserActions controller. */ @@ -80,6 +85,10 @@ export function buildUserActions( fetchUserActions(userId: string) { dispatch(fetchUserActions(userId)); }, + + logOpenUserActions() { + dispatch(logOpenUserActions()); + }, }; } diff --git a/packages/headless/src/features/analytics/insight-analytics-actions-loader.ts b/packages/headless/src/features/analytics/insight-analytics-actions-loader.ts index 58fcc360329..fff278f92a8 100644 --- a/packages/headless/src/features/analytics/insight-analytics-actions-loader.ts +++ b/packages/headless/src/features/analytics/insight-analytics-actions-loader.ts @@ -27,6 +27,7 @@ import {logNumericFacetBreadcrumb} from '../facets/range-facets/numeric-facet-se import { logInsightCreateArticle, CreateArticleMetadata, + logOpenUserActions, } from '../insight-search/insight-analytics-actions'; import { logInsightInterfaceChange, @@ -253,6 +254,13 @@ export interface InsightAnalyticsActionCreators { * @returns A dispatchable action. */ logFeedItemTextPost(result: Result): InsightAction; + + /** + * The event to log when opening the user actions panel. + * + * @returns A dispatchable action. + */ + logOpenUserActions(): InsightAction; } /** @@ -289,5 +297,6 @@ export function loadInsightAnalyticsActions( logCopyToClipboard, logCaseSendEmail, logFeedItemTextPost, + logOpenUserActions, }; } diff --git a/packages/headless/src/features/insight-search/insight-analytics-actions.test.ts b/packages/headless/src/features/insight-search/insight-analytics-actions.test.ts index 3c58d2570a1..1153552133a 100644 --- a/packages/headless/src/features/insight-search/insight-analytics-actions.test.ts +++ b/packages/headless/src/features/insight-search/insight-analytics-actions.test.ts @@ -8,10 +8,12 @@ import {getConfigurationInitialState} from '../configuration/configuration-state import { logExpandToFullUI, logInsightCreateArticle, + logOpenUserActions, } from './insight-analytics-actions'; const mockLogCreateArticle = jest.fn(); const mockLogExpandtoFullUI = jest.fn(); +const mockLogOpenUserActions = jest.fn(); const emit = jest.fn(); jest.mock('@coveo/relay'); @@ -21,6 +23,7 @@ jest.mock('coveo.analytics', () => { disable: jest.fn(), logExpandToFullUI: mockLogExpandtoFullUI, logCreateArticle: mockLogCreateArticle, + logOpenUserActions: mockLogOpenUserActions, })); return { @@ -104,6 +107,30 @@ describe('insight analytics actions', () => { }); }); + describe('logOpenUserActions', () => { + it('should call coveo.analytics.logOpenUserActions properly', async () => { + await logOpenUserActions()()( + engine.dispatch, + () => engine.state, + {} as ThunkExtraArguments + ); + + const expectedPayload = { + caseContext: { + Case_Subject: exampleSubject, + Case_Description: exampleDescription, + }, + caseId: exampleCaseId, + caseNumber: exampleCaseNumber, + }; + + expect(mockLogOpenUserActions).toHaveBeenCalledTimes(1); + expect(mockLogOpenUserActions.mock.calls[0][0]).toStrictEqual( + expectedPayload + ); + }); + }); + describe('logExpandToFullUI', () => { it('should call coveo.analytics.logExpandToFullUI properly', async () => { await logExpandToFullUI( diff --git a/packages/headless/src/features/insight-search/insight-analytics-actions.ts b/packages/headless/src/features/insight-search/insight-analytics-actions.ts index fcacf046d25..678e62abb6e 100644 --- a/packages/headless/src/features/insight-search/insight-analytics-actions.ts +++ b/packages/headless/src/features/insight-search/insight-analytics-actions.ts @@ -79,3 +79,13 @@ export const logInsightCreateArticle = ( }; }, }); + +export const logOpenUserActions = (): InsightAction => + makeInsightAnalyticsActionFactory('openUserActions')({ + prefix: 'analytics/insight/openUserActions', + __legacy__getBuilder: (client, state) => { + return client.logOpenUserActions( + getCaseContextAnalyticsMetadata(state.insightCaseContext) + ); + }, + }); diff --git a/packages/headless/src/features/insight-user-actions/insight-user-actions-actions.ts b/packages/headless/src/features/insight-user-actions/insight-user-actions-actions.ts index 74e92d80831..b5a94bd86d6 100644 --- a/packages/headless/src/features/insight-user-actions/insight-user-actions-actions.ts +++ b/packages/headless/src/features/insight-user-actions/insight-user-actions-actions.ts @@ -10,7 +10,7 @@ import { import {nonEmptyString, validatePayload} from '../../utils/validate-payload'; import {buildFetchUserActionsRequest} from './insight-user-actions-request'; -interface RegisterUserActionsPayload { +export interface RegisterUserActionsPayload { ticketCreationDate: string; excludedCustomActions?: string[]; } diff --git a/packages/headless/src/features/insight-user-actions/insight-user-actions-loader.ts b/packages/headless/src/features/insight-user-actions/insight-user-actions-loader.ts new file mode 100644 index 00000000000..f69c5bf12ec --- /dev/null +++ b/packages/headless/src/features/insight-user-actions/insight-user-actions-loader.ts @@ -0,0 +1,54 @@ +import {PayloadAction, AsyncThunkAction} from '@reduxjs/toolkit'; +import {AsyncThunkInsightOptions} from '../../api/service/insight/insight-api-client'; +import {CoreEngine} from '../../app/engine'; +import {insightUserActionsReducer as insightUserActions} from '../../features/insight-user-actions/insight-user-actions-slice'; +import { + registerUserActions, + fetchUserActions, + RegisterUserActionsPayload, + StateNeededByFetchUserActions, + FetchUserActionsThunkReturn, +} from './insight-user-actions-actions'; + +export type {RegisterUserActionsPayload}; +export interface InsightUserActionsActionCreators { + /** + * Registers the user actions for a given user ID, ticket creation date, and excluded custom actions. + * + * @param payload - The action creator payload. + * @returns A dispatchable action. + */ + registerUserActions( + payload: RegisterUserActionsPayload + ): PayloadAction; + + /** + * Fetches the user actions. + * + * @param userId - The user ID to fetch actions for. + * @returns A dispatchable action. + */ + fetchUserActions( + userId: string + ): AsyncThunkAction< + FetchUserActionsThunkReturn, + string, + AsyncThunkInsightOptions + >; +} + +/** + * Loads the `InsightUserActions` reducer and returns possible action creators. + * @param engine - The headless engine. + * @returns An object holding the action creators. + */ +export function loadInsightUserActionsActions( + engine: CoreEngine +): InsightUserActionsActionCreators { + engine.addReducers({insightUserActions}); + + return { + registerUserActions, + fetchUserActions, + }; +} diff --git a/packages/headless/src/insight.index.ts b/packages/headless/src/insight.index.ts index 3c4d75589ad..8bd84dbddbc 100644 --- a/packages/headless/src/insight.index.ts +++ b/packages/headless/src/insight.index.ts @@ -43,6 +43,7 @@ export * from './features/attached-results/attached-results-actions-loader'; export * from './features/analytics/generic-analytics-actions-loader'; export * from './features/question-answering/question-answering-actions-loader'; export * from './features/folding/folding-actions-loader'; +export * from './features/insight-user-actions/insight-user-actions-loader'; // Controllers export type { @@ -325,6 +326,17 @@ export type { } from './controllers/insight/generated-answer/headless-insight-interactive-citation'; export {buildInteractiveCitation} from './controllers/insight/generated-answer/headless-insight-interactive-citation'; export type {GeneratedAnswerStyle} from './features/generated-answer/generated-response-format'; + +export type { + UserActionsState, + UserActionsProps, + UserActionsOptions, + UserActions, + UserAction, + UserSession, +} from './controllers/insight/user-actions/headless-user-actions'; +export {buildUserActions} from './controllers/insight/user-actions/headless-user-actions'; + // Features export type { ResultTemplate,