Skip to content

Commit

Permalink
feat(headless): change citation from custom to click event (#4492)
Browse files Browse the repository at this point in the history
[SVCC-4170](https://coveord.atlassian.net/browse/SVCC-4170)

Change Citation Click from Custom Event to Click Event:

- Bump `coveo.analytics` to `2.30.39`.
- Use the new `logGeneratedAnswerCitationClick` event.
- This new method make the click event assume a `document position` of 1
for the purpose of the click event and click rank.
- Changes are made both in the search page client as well as the insight
client.

Future click event example:

```
{
    "language": "en",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "collectionName": "default", -- Deprecated
    "documentAuthor": "unknown", -- Available in stream response?
    "documentPosition": 1,  -- Hardcoded to 1
    "documentTitle": "Relevance Generative Answering", -- In stream API response
    "documentUri": "https://levelup.coveo.com/learn/courses/relevance-generative-answering",  -- In stream API response as URI
    "documentUriHash": "bK2L18lxLvsUB5h4", - Depecreated
    "documentUrl": "https://levelup.coveo.com/learn/courses/relevance-generative-answering", -- In stream API response as Click URI
    "sourceName": "Level Up",   -- Needs to be added stream API response
    "queryPipeline": "Customer Zero Community search - RGA Update V3",
    "originContext": "buildersearchpage", -- Same as custom event
    "originLevel1": "Connect RGA V3 Testing", -- Same as custom event
    "originLevel2": "default", -- Same as custom event
    "originLevel3": "https://search.cloud.coveo.com/builder/", -- Same as custom event
    "customData": {
        "coveoHeadlessVersion": "2.78.0", -- Inherited
        "generativeQuestionAnsweringId": "queryStream01_coveosearch_1ffa0550-3d4b-4d09-9574-8c4225b1edf9",
--TO ADD"contentIDKey": "permanentid",
--TO ADD"contentIDValue": "29bd12f1-12fb-4ac4-9463-66e37a0cb686",
        "coveoAtomicVersion": "2.77.1" -- Inherited
--From Stream API"citationId": "42.20035$https://levelup.coveo.com/learn/courses/relevance-generative-answering-3541ae00-8d76-4b85-aa02-75478f4b6a6e",
    },
    "facetState": [],
    "anonymous": false,
    "clientId": "d4a6b215-241a-40f2-ad82-4890895843ad",
    "actionCause": "openGeneratedAnswerSource", -- New actionCause
    "searchQueryUid": "6549c762-4b4d-466d-ba16-bfe3b8d21928" -- From search event
}
```

[SVCC-4170]:
https://coveord.atlassian.net/browse/SVCC-4170?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
jelmedini authored Oct 9, 2024
1 parent 6d17ed7 commit 1cc5aee
Show file tree
Hide file tree
Showing 15 changed files with 149 additions and 49 deletions.
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/atomic/cypress/e2e/generated-answer-assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {should} from './common-assertions';
import {GeneratedAnswerSelectors} from './generated-answer-selectors';

export function assertLogOpenGeneratedAnswerSource() {
cy.expectCustomEvent('generatedAnswer', 'openGeneratedAnswerSource');
cy.expectClickEvent('generatedAnswerCitationClick');
}

export function assertLogGeneratedAnswerSourceHover() {
Expand Down
2 changes: 1 addition & 1 deletion packages/headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
"@microsoft/fetch-event-source": "2.0.1",
"@reduxjs/toolkit": "2.2.7",
"abortcontroller-polyfill": "1.7.5",
"coveo.analytics": "2.30.38",
"coveo.analytics": "2.30.39",
"dayjs": "1.11.12",
"exponential-backoff": "3.1.0",
"fast-equals": "5.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface GeneratedAnswerCitation {
title: string;
uri: string;
permanentid: string;
source: string;
clickUri?: string;
text?: string;
fields?: Raw;
Expand Down
33 changes: 33 additions & 0 deletions packages/headless/src/features/analytics/analytics-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
SearchAnalyticsProvider,
StateNeededBySearchAnalyticsProvider,
} from '../../api/analytics/search-analytics.js';
import {GeneratedAnswerCitation} from '../../api/generated-answer/generated-answer-event-payload.js';
import {PreprocessRequest} from '../../api/preprocess-request.js';
import {Raw} from '../../api/search/search/raw.js';
import {Result} from '../../api/search/search/result.js';
Expand Down Expand Up @@ -623,6 +624,38 @@ export const partialRecommendationInformation = (

return buildPartialDocumentInformation(result, resultIndex, state);
};

export const partialCitationInformation = (
citation: GeneratedAnswerCitation,
state: Partial<SearchAppState>
): PartialDocumentInformation => {
return {
sourceName: getCitationSourceName(citation),
documentPosition: 1,
documentTitle: citation.title,
documentUri: citation.uri,
documentUrl: citation.clickUri,
queryPipeline: state.pipeline || getPipelineInitialState(),
};
};

function getCitationSourceName(citation: GeneratedAnswerCitation) {
const source = citation.source;
if (isNullOrUndefined(source)) {
return 'unknown';
}
return source;
}

export const citationDocumentIdentifier = (
citation: GeneratedAnswerCitation
) => {
return {
contentIdKey: 'permanentid',
contentIdValue: citation.permanentid || '',
};
};

function buildPartialDocumentInformation(
result: Result,
resultIndex: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ exports[`generated answer analytics actions > when analyticsMode is \`next\` > s
"title": "Sample citation",
"uniqueFieldName": "permanentid",
"uniqueFieldValue": "citation-permanent-id",
"url": undefined,
"url": "http://localhost/citations/some-citation-id",
},
"responseId": "123",
},
Expand All @@ -114,7 +114,7 @@ exports[`generated answer analytics actions > when analyticsMode is \`next\` > s
"title": "Sample citation",
"uniqueFieldName": "permanentid",
"uniqueFieldValue": "citation-permanent-id",
"url": undefined,
"url": "http://localhost/citations/some-citation-id",
},
"responseId": "123",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ exports[`generated answer insight analytics actions > when analyticsMode is \`ne
"title": "example title",
"uniqueFieldName": "permanentid",
"uniqueFieldValue": "citation_permanentid",
"url": undefined,
"url": "example: click uri",
},
"responseId": "123",
},
Expand All @@ -114,7 +114,7 @@ exports[`generated answer insight analytics actions > when analyticsMode is \`ne
"title": "example title",
"uniqueFieldName": "permanentid",
"uniqueFieldValue": "citation_permanentid",
"url": undefined,
"url": "example: click uri",
},
"responseId": "123",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const mockMakeGeneratedAnswerFeedbackSubmit = vi.fn(() => ({
const mockMakeRetryGeneratedAnswer = vi.fn(() => ({
log: mockLogFunction,
}));
const mockMakeOpenGeneratedAnswerSource = vi.fn(() => ({
const mockMakeGeneratedAnswerCitationClick = vi.fn((..._args) => ({
log: mockLogFunction,
}));
const mockMakeGeneratedAnswerSourceHover = vi.fn(() => ({
Expand Down Expand Up @@ -84,7 +84,7 @@ vi.mock('coveo.analytics', () => {
disable: vi.fn(),
makeGeneratedAnswerFeedbackSubmit: mockMakeGeneratedAnswerFeedbackSubmit,
makeRetryGeneratedAnswer: mockMakeRetryGeneratedAnswer,
makeOpenGeneratedAnswerSource: mockMakeOpenGeneratedAnswerSource,
makeGeneratedAnswerCitationClick: mockMakeGeneratedAnswerCitationClick,
makeGeneratedAnswerSourceHover: mockMakeGeneratedAnswerSourceHover,
makeLikeGeneratedAnswer: mockMakeLikeGeneratedAnswer,
makeDislikeGeneratedAnswer: mockMakeDislikeGeneratedAnswer,
Expand Down Expand Up @@ -119,6 +119,22 @@ const exampleCitation: GeneratedAnswerCitation = {
permanentid: 'citation-permanent-id',
title: 'Sample citation',
uri: 'http://localhost/citations/some-citation-id',
source: 'source-name',
clickUri: 'http://localhost/citations/some-citation-id',
};

const expectedCitationDocumentInfo = {
queryPipeline: '',
documentUri: exampleCitation.uri,
sourceName: exampleCitation.source,
documentPosition: 1,
documentTitle: exampleCitation.title,
documentUrl: exampleCitation.clickUri,
};

const exampleDocumentId = {
contentIdKey: 'permanentid',
contentIdValue: exampleCitation.permanentid,
};

describe('generated answer analytics actions', () => {
Expand Down Expand Up @@ -177,14 +193,20 @@ describe('generated answer analytics actions', () => {
{} as ThunkExtraArguments
);

const mockToUse = mockMakeOpenGeneratedAnswerSource;
const mockToUse = mockMakeGeneratedAnswerCitationClick;

expect(mockToUse).toHaveBeenCalledTimes(1);
expect(mockToUse).toHaveBeenCalledWith({

expect(mockToUse.mock.calls[0][0]).toStrictEqual(
expectedCitationDocumentInfo
);

expect(mockToUse.mock.calls[0][1]).toStrictEqual({
generativeQuestionAnsweringId: exampleGenerativeQuestionAnsweringId,
citationId: exampleCitation.id,
permanentId: exampleCitation.permanentid,
documentId: exampleDocumentId,
});

expect(mockLogFunction).toHaveBeenCalledTimes(1);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {Rga} from '@coveo/relay-event-types';
import {
citationDocumentIdentifier,
CustomAction,
LegacySearchAction,
makeAnalyticsAction,
partialCitationInformation,
} from '../analytics/analytics-utils.js';
import {SearchPageEvents} from '../analytics/search-action-cause.js';
import {SearchAction} from '../search/search-actions.js';
Expand Down Expand Up @@ -54,13 +56,16 @@ export const logOpenGeneratedAnswerSource = (
return null;
}

return client.makeOpenGeneratedAnswerSource({
...(answerAPIEnabled
? {answerAPIStreamId: rgaID}
: {generativeQuestionAnsweringId: rgaID}),
permanentId: citation.permanentid,
citationId: citation.id,
});
return client.makeGeneratedAnswerCitationClick(
partialCitationInformation(citation, state),
{
...(answerAPIEnabled
? {answerAPIStreamId: rgaID}
: {generativeQuestionAnsweringId: rgaID}),
citationId: citation.id,
documentId: citationDocumentIdentifier(citation),
}
);
},
analyticsType: 'Rga.CitationClick',
analyticsPayloadBuilder: (state): Rga.CitationClick | undefined => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {getGeneratedAnswerInitialState} from './generated-answer-state.js';

const mockLogGeneratedAnswerFeedbackSubmit = vi.fn();
const mockLogRetryGeneratedAnswer = vi.fn();
const mockLogOpenGeneratedAnswerSource = vi.fn();
const mockLogGeneratedAnswerCitationClick = vi.fn();
const mockLogHoverCitation = vi.fn();
const mockLogLikeGeneratedAnswer = vi.fn();
const mockLogDislikeGeneratedAnswer = vi.fn();
Expand Down Expand Up @@ -56,7 +56,7 @@ vi.mock('coveo.analytics', () => {
disable: vi.fn(),
logGeneratedAnswerFeedbackSubmit: mockLogGeneratedAnswerFeedbackSubmit,
logRetryGeneratedAnswer: mockLogRetryGeneratedAnswer,
logOpenGeneratedAnswerSource: mockLogOpenGeneratedAnswerSource,
logGeneratedAnswerCitationClick: mockLogGeneratedAnswerCitationClick,
logGeneratedAnswerSourceHover: mockLogHoverCitation,
logLikeGeneratedAnswer: mockLogLikeGeneratedAnswer,
logDislikeGeneratedAnswer: mockLogDislikeGeneratedAnswer,
Expand Down Expand Up @@ -90,6 +90,10 @@ const exampleSubject = 'example subject';
const exampleDescription = 'example description';
const exampleCaseId = '1234';
const exampleCaseNumber = '5678';
const exampleCitationTitle = 'example title';
const exampleCitationUri = 'example: uri';
const exampleCitationSource = 'example source name';
const exampleCitationClickUri = 'example: click uri';

const expectedCaseContext = {
caseContext: {
Expand All @@ -100,6 +104,20 @@ const expectedCaseContext = {
caseNumber: exampleCaseNumber,
};

const expectedCitationDocumentInfo = {
queryPipeline: '',
documentUri: exampleCitationUri,
sourceName: exampleCitationSource,
documentPosition: 1,
documentTitle: exampleCitationTitle,
documentUrl: exampleCitationClickUri,
};

const exampleDocumentId = {
contentIdKey: 'permanentid',
contentIdValue: exampleCitationPermanentid,
};

describe('generated answer insight analytics actions', () => {
let engine: MockedInsightEngine;
const searchState = buildMockSearchState({
Expand All @@ -116,8 +134,10 @@ describe('generated answer insight analytics actions', () => {
{
id: exampleCitationId,
permanentid: exampleCitationPermanentid,
title: 'example title',
uri: 'example: uri',
title: exampleCitationTitle,
uri: exampleCitationUri,
source: exampleCitationSource,
clickUri: exampleCitationClickUri,
},
],
};
Expand Down Expand Up @@ -172,18 +192,22 @@ describe('generated answer insight analytics actions', () => {
{} as ThunkExtraArguments
);

const mockToUse = mockLogOpenGeneratedAnswerSource;
const mockToUse = mockLogGeneratedAnswerCitationClick;
const expectedMetadata = {
generativeQuestionAnsweringId: exampleGenerativeQuestionAnsweringId,
permanentId: exampleCitationPermanentid,
citationId: exampleCitationId,
documentId: exampleDocumentId,
};

expect(mockToUse).toHaveBeenCalledTimes(1);
expect(mockToUse).toHaveBeenCalledWith(
expectedMetadata,
expectedCaseContext

expect(mockToUse.mock.calls[0][0]).toStrictEqual(
expectedCitationDocumentInfo
);

expect(mockToUse.mock.calls[0][1]).toStrictEqual(expectedMetadata);

expect(mockToUse.mock.calls[0][2]).toStrictEqual(expectedCaseContext);
});

it('should log #logHoverCitation with the right payload', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {Rga} from '@coveo/relay-event-types';
import {
citationDocumentIdentifier,
InsightAction,
makeInsightAnalyticsActionFactory,
partialCitationInformation,
} from '../analytics/analytics-utils.js';
import {SearchPageEvents} from '../analytics/search-action-cause.js';
import {getCaseContextAnalyticsMetadata} from '../case-context/case-context-state.js';
Expand Down Expand Up @@ -38,13 +40,14 @@ export const logOpenGeneratedAnswerSource = (
if (!rgaID || !citation) {
return null;
}
return client.logOpenGeneratedAnswerSource(
return client.logGeneratedAnswerCitationClick(
partialCitationInformation(citation, state),
{
...(answerAPIEnabled
? {answerAPIStreamId: rgaID}
: {generativeQuestionAnsweringId: rgaID}),
permanentId: citation.permanentid,
citationId: citation.id,
documentId: citationDocumentIdentifier(citation),
},
getCaseContextAnalyticsMetadata(state.insightCaseContext)
);
Expand Down
1 change: 1 addition & 0 deletions packages/headless/src/test/mock-citation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function buildMockCitation(
uri: '',
permanentid: '',
clickUri: '',
source: '',
text: '',
...config,
};
Expand Down
Loading

0 comments on commit 1cc5aee

Please sign in to comment.