@coveo/quantic 3.10.0 → 3.11.1
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/README.md +52 -12
- package/docs/out/quantic-docs.json +27 -0
- package/force-app/main/default/labels/CustomLabels.labels-meta.xml +7 -0
- package/force-app/main/default/lwc/quanticBreadcrumbManager/__tests__/quanticBreadcrumbManager.test.js +341 -0
- package/force-app/main/default/lwc/quanticBreadcrumbManager/quanticBreadcrumbManager.html +8 -8
- package/force-app/main/default/lwc/quanticBreadcrumbManager/quanticBreadcrumbManager.js +8 -8
- package/force-app/main/default/lwc/quanticFeedbackModalQna/templates/quanticFeedbackModalQna.html +1 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +538 -21
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/data.ts +56 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/fixture.ts +87 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/pageObject.ts +279 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswer.e2e.ts +178 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +24 -10
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/retryPrompt.html +2 -2
- package/force-app/main/default/lwc/quanticGeneratedAnswerToggle/quanticGeneratedAnswerToggle.html +1 -1
- package/force-app/main/default/lwc/quanticNotifications/__tests__/quanticNotifications.test.js +112 -4
- package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.css +8 -0
- package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.html +20 -7
- package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.js +57 -18
- package/force-app/main/default/lwc/quanticPager/e2e/pagerObject.ts +1 -1
- package/force-app/main/default/lwc/quanticUserActionsToggle/__tests__/quanticUserActionsToggle.test.js +18 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/data.ts +32 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/fixture.ts +32 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/pageObject.ts +71 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/quanticUserActionsToggle.e2e.ts +39 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/quanticUserActionsToggle.js +4 -0
- package/force-app/main/default/staticresources/coveobueno/browser/bueno.js +4 -4
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +8 -10
- package/force-app/main/default/staticresources/coveoheadless/definitions/api/analytics/instant-result-analytics.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/api/analytics/search-analytics.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/api/knowledge/stream-answer-api.d.ts +3 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-engine/commerce-engine.ssr.d.ts +2 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/build-factory.d.ts +2 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/utils/recommendation-filter.d.ts +11 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/build.d.ts +4 -8
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/common.d.ts +178 -7
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/core-engine.d.ts +3 -3
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/fetch-static-state.d.ts +4 -12
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/from-build-result.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/hydrate-static-state.d.ts +4 -11
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/kind.d.ts +6 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/ssr-engine/types/common.d.ts +6 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/commerce.index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/context/cart/headless-cart.ssr.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/context/headless-context.ssr.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.d.ts +1 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.d.ts +3 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.d.ts +8 -7
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.d.ts +3 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/sub-controller/headless-sub-controller.d.ts +1 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/url-manager/headless-core-url-manager.d.ts +11 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-list/headless-product-list.ssr.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-view/headless-product-view.ssr.d.ts +1 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recommendations/headless-recommendations.d.ts +1 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recommendations/headless-recommendations.ssr.d.ts +4 -4
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/standalone-search-box/headless-standalone-search-box.ssr.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-actions.d.ts +22 -6
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-schema.d.ts +4 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-selectors.d.ts +0 -11
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-serializer.d.ts +6 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-serializer.ssr.d.ts +44 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-slice.d.ts +4 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-state.d.ts +4 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/recommendations/recommendations-actions.d.ts +1 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/recommendations/recommendations-state.d.ts +1 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/search/search-selectors.d.ts +0 -12
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/debug/debug-selectors.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/question-answering/question-answering-selectors.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/search/search-selectors.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/search-parameters/search-parameter-serializer.d.ts +5 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +4 -3
- package/force-app/main/default/staticresources/coveoheadless/definitions/state/commerce-app-state.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/state/state-sections.d.ts +4 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/utils/errors.d.ts +3 -0
- package/force-app/main/default/staticresources/coveoheadless/headless.js +10 -12
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +9 -11
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +8 -10
- package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -1339
- package/package.json +23 -19
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
|
|
2
|
+
import {SearchObject} from '../../../../../../playwright/page-object/searchObject';
|
|
3
|
+
import {
|
|
4
|
+
searchRequestRegex,
|
|
5
|
+
insightSearchRequestRegex,
|
|
6
|
+
} from '../../../../../../playwright/utils/requests';
|
|
7
|
+
import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
|
|
8
|
+
import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
|
|
9
|
+
import {GeneratedAnswerObject} from './pageObject';
|
|
10
|
+
import genQaData from './data';
|
|
11
|
+
import type {GenQaData} from './data';
|
|
12
|
+
|
|
13
|
+
const pageUrl = 's/quantic-generated-answer';
|
|
14
|
+
|
|
15
|
+
interface GeneratedAnswerOptions {
|
|
16
|
+
fieldsToIncludeInCitations: string;
|
|
17
|
+
collapsible: boolean;
|
|
18
|
+
withToggle: boolean;
|
|
19
|
+
useCase: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type QuanticGeneratedAnswerE2ESearchFixtures = {
|
|
23
|
+
genQaData: GenQaData;
|
|
24
|
+
generatedAnswer: GeneratedAnswerObject;
|
|
25
|
+
search: SearchObject;
|
|
26
|
+
options: Partial<GeneratedAnswerOptions>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type QuanticGeneratedAnswerE2EInsightFixtures =
|
|
30
|
+
QuanticGeneratedAnswerE2ESearchFixtures & {
|
|
31
|
+
insightSetup: InsightSetupObject;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const testSearch =
|
|
35
|
+
quanticBase.extend<QuanticGeneratedAnswerE2ESearchFixtures>({
|
|
36
|
+
genQaData,
|
|
37
|
+
options: {},
|
|
38
|
+
search: async ({page}, use) => {
|
|
39
|
+
await use(new SearchObject(page, searchRequestRegex));
|
|
40
|
+
},
|
|
41
|
+
generatedAnswer: async (
|
|
42
|
+
{page, options, configuration, search, genQaData: data},
|
|
43
|
+
use
|
|
44
|
+
) => {
|
|
45
|
+
const generatedAnswerObject = new GeneratedAnswerObject(
|
|
46
|
+
page,
|
|
47
|
+
data.streamId
|
|
48
|
+
);
|
|
49
|
+
await page.goto(pageUrl);
|
|
50
|
+
await search.mockSearchWithGenerativeQuestionAnsweringId(data.streamId);
|
|
51
|
+
await generatedAnswerObject.mockStreamResponse(data.streams);
|
|
52
|
+
await configuration.configure(options);
|
|
53
|
+
await search.waitForSearchResponse();
|
|
54
|
+
await use(generatedAnswerObject);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const testInsight =
|
|
59
|
+
quanticBase.extend<QuanticGeneratedAnswerE2EInsightFixtures>({
|
|
60
|
+
genQaData,
|
|
61
|
+
options: {},
|
|
62
|
+
search: async ({page}, use) => {
|
|
63
|
+
await use(new SearchObject(page, insightSearchRequestRegex));
|
|
64
|
+
},
|
|
65
|
+
insightSetup: async ({page}, use) => {
|
|
66
|
+
await use(new InsightSetupObject(page));
|
|
67
|
+
},
|
|
68
|
+
generatedAnswer: async (
|
|
69
|
+
{page, options, search, configuration, insightSetup, genQaData: data},
|
|
70
|
+
use
|
|
71
|
+
) => {
|
|
72
|
+
const generatedAnswerObject = new GeneratedAnswerObject(
|
|
73
|
+
page,
|
|
74
|
+
data.streamId
|
|
75
|
+
);
|
|
76
|
+
await page.goto(pageUrl);
|
|
77
|
+
await search.mockSearchWithGenerativeQuestionAnsweringId(data.streamId);
|
|
78
|
+
await generatedAnswerObject.mockStreamResponse(data.streams);
|
|
79
|
+
configuration.configure({...options, useCase: useCaseEnum.insight});
|
|
80
|
+
await insightSetup.waitForInsightInterfaceInitialization();
|
|
81
|
+
await search.performSearch();
|
|
82
|
+
await search.waitForSearchResponse();
|
|
83
|
+
await use(generatedAnswerObject);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export {expect} from '@playwright/test';
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import type {Locator, Page, Request} from '@playwright/test';
|
|
2
|
+
import {
|
|
3
|
+
isUaClickEvent,
|
|
4
|
+
isUaCustomEvent,
|
|
5
|
+
} from '../../../../../../playwright/utils/requests';
|
|
6
|
+
|
|
7
|
+
const minimumCitationTooltipDisplayDurationMs = 1500;
|
|
8
|
+
|
|
9
|
+
export class GeneratedAnswerObject {
|
|
10
|
+
constructor(
|
|
11
|
+
public page: Page,
|
|
12
|
+
public streamId: string
|
|
13
|
+
) {
|
|
14
|
+
this.page = page;
|
|
15
|
+
this.streamId = streamId;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get likeButton(): Locator {
|
|
19
|
+
return this.page.getByRole('button', {name: 'This answer was helpful'});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get dislikeButton(): Locator {
|
|
23
|
+
return this.page.getByRole('button', {name: 'This answer was not helpful'});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get copyToClipboardButton(): Locator {
|
|
27
|
+
return this.page.getByRole('button', {name: 'Copy'});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get toggleButton(): Locator {
|
|
31
|
+
return this.page.getByTestId('generated-answer__toggle-button');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
questionContainer(questionId: string): Locator {
|
|
35
|
+
return this.page.getByTestId(questionId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
answerOption(questionId: string, answerValue: string): Locator {
|
|
39
|
+
return this.questionContainer(questionId).locator('.slds-radio_button', {
|
|
40
|
+
hasText: new RegExp(`^${answerValue}$`),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get feedbackDocumentUrlInput(): Locator {
|
|
45
|
+
return this.page.locator(
|
|
46
|
+
'.feedback-modal-qna__body input[name="documentUrl"]'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get feedbackDetailsInput(): Locator {
|
|
51
|
+
return this.page
|
|
52
|
+
.locator('.feedback-modal-qna__body [data-name="details"]')
|
|
53
|
+
.getByRole('textbox');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get submitFeedbackButton(): Locator {
|
|
57
|
+
return this.page.getByRole('button', {name: /Send feedback/i});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get citationLink(): Locator {
|
|
61
|
+
return this.page
|
|
62
|
+
.getByTestId('generated-answer__citations')
|
|
63
|
+
.locator('.citation__link');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async hoverOverCitation(index: number): Promise<void> {
|
|
67
|
+
// waiting 500ms to allow the component to render completely, cause any re-rendering abort the hover action.
|
|
68
|
+
await this.page.waitForTimeout(500);
|
|
69
|
+
await this.citationLink.nth(index).hover();
|
|
70
|
+
await this.page.waitForTimeout(minimumCitationTooltipDisplayDurationMs);
|
|
71
|
+
await this.page.mouse.move(0, 0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async clickOnCitation(index: number): Promise<void> {
|
|
75
|
+
await this.citationLink.nth(index).click();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async typeInFeedbackDocumentUrlInput(text: string): Promise<void> {
|
|
79
|
+
await this.feedbackDocumentUrlInput.fill(text);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async typeInFeedbackDetailsInput(text: string): Promise<void> {
|
|
83
|
+
await this.feedbackDetailsInput.fill(text);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async clickSubmitFeedbackButton(): Promise<void> {
|
|
87
|
+
await this.submitFeedbackButton.click();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async fillFeedbackForm(answers: Record<string, string>): Promise<void> {
|
|
91
|
+
for (const [questionId, answerValue] of Object.entries(answers)) {
|
|
92
|
+
const option = this.answerOption(questionId, answerValue);
|
|
93
|
+
// eslint-disable-next-line no-await-in-loop
|
|
94
|
+
await option.click();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async clickLikeButton(): Promise<void> {
|
|
99
|
+
await this.likeButton.click();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async clickDislikeButton(): Promise<void> {
|
|
103
|
+
await this.dislikeButton.click();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async clickCopyToClipboardButton(): Promise<void> {
|
|
107
|
+
await this.copyToClipboardButton.click();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async clickToggleButton(): Promise<void> {
|
|
111
|
+
await this.toggleButton.click();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async waitForStreamEndUaAnalytics(): Promise<Request> {
|
|
115
|
+
return this.waitForGeneratedAnswerCustomUaAnalytics(
|
|
116
|
+
'generatedAnswerStreamEnd'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async waitForLikeGeneratedAnswerUaAnalytics(): Promise<Request> {
|
|
121
|
+
return this.waitForGeneratedAnswerCustomUaAnalytics('likeGeneratedAnswer');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async waitForDislikeGeneratedAnswerUaAnalytics(): Promise<Request> {
|
|
125
|
+
return this.waitForGeneratedAnswerCustomUaAnalytics(
|
|
126
|
+
'dislikeGeneratedAnswer'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async waitForCopyToClipboardUaAnalytics(): Promise<Request> {
|
|
131
|
+
return this.waitForGeneratedAnswerCustomUaAnalytics(
|
|
132
|
+
'generatedAnswerCopyToClipboard'
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async waitForShowAnswersUaAnalytics(): Promise<Request> {
|
|
137
|
+
return this.waitForGeneratedAnswerCustomUaAnalytics(
|
|
138
|
+
'generatedAnswerShowAnswers'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async waitForHideAnswersUaAnalytics(): Promise<Request> {
|
|
143
|
+
return this.waitForGeneratedAnswerCustomUaAnalytics(
|
|
144
|
+
'generatedAnswerHideAnswers'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async waitForSourceHoverUaAnalytics(
|
|
149
|
+
expectedFields: Record<string, any>
|
|
150
|
+
): Promise<Request> {
|
|
151
|
+
return this.waitForGeneratedAnswerCustomUaAnalytics(
|
|
152
|
+
'generatedAnswerSourceHover',
|
|
153
|
+
(customData: Record<string, any>) => {
|
|
154
|
+
return Object.keys(expectedFields).every(
|
|
155
|
+
(key) => customData?.[key] === expectedFields[key]
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async waitForFeedbackSubmitUaAnalytics(
|
|
162
|
+
expectedFields: Record<string, any>
|
|
163
|
+
): Promise<Request> {
|
|
164
|
+
return this.waitForGeneratedAnswerCustomUaAnalytics(
|
|
165
|
+
'generatedAnswerFeedbackSubmitV2',
|
|
166
|
+
(customData: Record<string, any>) => {
|
|
167
|
+
return Object.keys(expectedFields).every(
|
|
168
|
+
(key) => customData?.[key] === expectedFields[key]
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async waitForCitationClickUaAnalytics(
|
|
175
|
+
expectedFields: Record<string, any>,
|
|
176
|
+
expectedCustomFields: Record<string, any>
|
|
177
|
+
): Promise<Request> {
|
|
178
|
+
return this.waitForGeneratedAnswerClickUaAnalytics(
|
|
179
|
+
'generatedAnswerCitationClick',
|
|
180
|
+
(data: Record<string, any>, customData: Record<string, any>) => {
|
|
181
|
+
return (
|
|
182
|
+
Object.keys(expectedFields).every(
|
|
183
|
+
(key) => data?.[key] === expectedFields[key]
|
|
184
|
+
) &&
|
|
185
|
+
Object.keys(expectedCustomFields).every(
|
|
186
|
+
(key) => customData?.[key] === expectedCustomFields[key]
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async waitForGeneratedAnswerClickUaAnalytics(
|
|
194
|
+
actionCause: string,
|
|
195
|
+
customChecker?: Function
|
|
196
|
+
): Promise<Request> {
|
|
197
|
+
const uaRequest = this.page.waitForRequest((request) => {
|
|
198
|
+
if (isUaClickEvent(request)) {
|
|
199
|
+
const requestBody = request.postDataJSON?.();
|
|
200
|
+
const requestData = JSON.parse(requestBody.clickEvent);
|
|
201
|
+
|
|
202
|
+
const expectedFields: Record<string, any> = {
|
|
203
|
+
actionCause,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const matchesExpectedFields = Object.keys(expectedFields).every(
|
|
207
|
+
(key) => requestData?.[key] === expectedFields[key]
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const customData = requestData?.customData;
|
|
211
|
+
|
|
212
|
+
const matchesGenerativeId =
|
|
213
|
+
customData?.generativeQuestionAnsweringId === this.streamId;
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
matchesExpectedFields &&
|
|
217
|
+
matchesGenerativeId &&
|
|
218
|
+
(customChecker ? customChecker(requestData, customData) : true)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
});
|
|
223
|
+
return uaRequest;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async waitForGeneratedAnswerCustomUaAnalytics(
|
|
227
|
+
eventValue: string,
|
|
228
|
+
customChecker?: Function
|
|
229
|
+
): Promise<Request> {
|
|
230
|
+
const uaRequest = this.page.waitForRequest((request) => {
|
|
231
|
+
if (isUaCustomEvent(request)) {
|
|
232
|
+
const requestBody = request.postDataJSON?.();
|
|
233
|
+
const expectedFields: Record<string, any> = {
|
|
234
|
+
eventType: 'generatedAnswer',
|
|
235
|
+
eventValue: eventValue,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const matchesExpectedFields = Object.keys(expectedFields).every(
|
|
239
|
+
(key) => requestBody?.[key] === expectedFields[key]
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const customData = requestBody?.customData;
|
|
243
|
+
|
|
244
|
+
const matchesGenerativeId =
|
|
245
|
+
customData?.generativeQuestionAnsweringId === this.streamId;
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
matchesExpectedFields &&
|
|
249
|
+
matchesGenerativeId &&
|
|
250
|
+
(customChecker ? customChecker(customData) : true)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
});
|
|
255
|
+
return uaRequest;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async mockStreamResponse(
|
|
259
|
+
body: Array<{payloadType: string; payload: string; finishReason?: string}>
|
|
260
|
+
) {
|
|
261
|
+
await this.page.route(
|
|
262
|
+
`**/machinelearning/streaming/${this.streamId}`,
|
|
263
|
+
(route) => {
|
|
264
|
+
let bodyText = '';
|
|
265
|
+
body.forEach((data) => {
|
|
266
|
+
bodyText += `data: ${JSON.stringify(data)} \n\n`;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
route.fulfill({
|
|
270
|
+
status: 200,
|
|
271
|
+
body: bodyText,
|
|
272
|
+
headers: {
|
|
273
|
+
'content-type': 'text/event-stream',
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {testSearch, testInsight} from './fixture';
|
|
2
|
+
import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
|
|
3
|
+
import genQaData from './data';
|
|
4
|
+
|
|
5
|
+
const fixtures = {
|
|
6
|
+
search: testSearch,
|
|
7
|
+
insight: testInsight,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
useCaseTestCases.forEach((useCase) => {
|
|
11
|
+
let test = fixtures[useCase.value];
|
|
12
|
+
|
|
13
|
+
test.describe(`quantic generated answer ${useCase.label}`, () => {
|
|
14
|
+
test.use({
|
|
15
|
+
genQaData,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test.describe('when the answer has been generated', () => {
|
|
19
|
+
test('should send a stream end analytics event', async ({
|
|
20
|
+
generatedAnswer,
|
|
21
|
+
}) => {
|
|
22
|
+
await generatedAnswer.waitForStreamEndUaAnalytics();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test.describe('when providing positive feedback', () => {
|
|
27
|
+
test('should send positive feedback analytics containing all details', async ({
|
|
28
|
+
generatedAnswer,
|
|
29
|
+
}) => {
|
|
30
|
+
const likeAnalyticRequestPromise =
|
|
31
|
+
generatedAnswer.waitForLikeGeneratedAnswerUaAnalytics();
|
|
32
|
+
await generatedAnswer.clickLikeButton();
|
|
33
|
+
await likeAnalyticRequestPromise;
|
|
34
|
+
|
|
35
|
+
const exampleDocumentUrl = 'https://www.coveo.com/';
|
|
36
|
+
const exampleDetails = 'example details...';
|
|
37
|
+
await generatedAnswer.fillFeedbackForm({
|
|
38
|
+
correctTopic: 'Yes',
|
|
39
|
+
hallucinationFree: 'Yes',
|
|
40
|
+
documented: 'Not sure',
|
|
41
|
+
readable: 'Yes',
|
|
42
|
+
});
|
|
43
|
+
await generatedAnswer.typeInFeedbackDocumentUrlInput(
|
|
44
|
+
exampleDocumentUrl
|
|
45
|
+
);
|
|
46
|
+
await generatedAnswer.typeInFeedbackDetailsInput(exampleDetails);
|
|
47
|
+
|
|
48
|
+
const feedbackAnalyticRequestPromise =
|
|
49
|
+
generatedAnswer.waitForFeedbackSubmitUaAnalytics({
|
|
50
|
+
correctTopic: 'yes',
|
|
51
|
+
hallucinationFree: 'yes',
|
|
52
|
+
documented: 'unknown',
|
|
53
|
+
readable: 'yes',
|
|
54
|
+
documentUrl: exampleDocumentUrl,
|
|
55
|
+
details: exampleDetails,
|
|
56
|
+
helpful: true,
|
|
57
|
+
});
|
|
58
|
+
await generatedAnswer.clickSubmitFeedbackButton();
|
|
59
|
+
await feedbackAnalyticRequestPromise;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test.describe('when providing negative feedback', () => {
|
|
64
|
+
test('should send negative feedback analytics containing all details', async ({
|
|
65
|
+
generatedAnswer,
|
|
66
|
+
}) => {
|
|
67
|
+
const dislikeAnalyticRequestPromise =
|
|
68
|
+
generatedAnswer.waitForDislikeGeneratedAnswerUaAnalytics();
|
|
69
|
+
await generatedAnswer.clickDislikeButton();
|
|
70
|
+
await dislikeAnalyticRequestPromise;
|
|
71
|
+
|
|
72
|
+
const exampleDocumentUrl = 'https://www.coveo.com/';
|
|
73
|
+
const exampleDetails = 'example details...';
|
|
74
|
+
await generatedAnswer.fillFeedbackForm({
|
|
75
|
+
correctTopic: 'No',
|
|
76
|
+
hallucinationFree: 'Not sure',
|
|
77
|
+
documented: 'No',
|
|
78
|
+
readable: 'No',
|
|
79
|
+
});
|
|
80
|
+
await generatedAnswer.typeInFeedbackDocumentUrlInput(
|
|
81
|
+
exampleDocumentUrl
|
|
82
|
+
);
|
|
83
|
+
await generatedAnswer.typeInFeedbackDetailsInput(exampleDetails);
|
|
84
|
+
|
|
85
|
+
const feedbackAnalyticRequestPromise =
|
|
86
|
+
generatedAnswer.waitForFeedbackSubmitUaAnalytics({
|
|
87
|
+
correctTopic: 'no',
|
|
88
|
+
hallucinationFree: 'unknown',
|
|
89
|
+
documented: 'no',
|
|
90
|
+
readable: 'no',
|
|
91
|
+
documentUrl: exampleDocumentUrl,
|
|
92
|
+
details: exampleDetails,
|
|
93
|
+
helpful: false,
|
|
94
|
+
});
|
|
95
|
+
await generatedAnswer.clickSubmitFeedbackButton();
|
|
96
|
+
await feedbackAnalyticRequestPromise;
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test.describe('when copying the generated answer to clipboard', () => {
|
|
101
|
+
test('should send a copy to clipboard analytics event', async ({
|
|
102
|
+
generatedAnswer,
|
|
103
|
+
}) => {
|
|
104
|
+
const analyticRequestPromise =
|
|
105
|
+
generatedAnswer.waitForCopyToClipboardUaAnalytics();
|
|
106
|
+
await generatedAnswer.clickCopyToClipboardButton();
|
|
107
|
+
await analyticRequestPromise;
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test.describe('when the property withToggle is set to true', () => {
|
|
112
|
+
test.use({
|
|
113
|
+
options: {
|
|
114
|
+
withToggle: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('should allow toggeling the generated OFF and ON and log analytics', async ({
|
|
119
|
+
generatedAnswer,
|
|
120
|
+
}) => {
|
|
121
|
+
const hideAnswerAnalyticRequestPromise =
|
|
122
|
+
generatedAnswer.waitForHideAnswersUaAnalytics();
|
|
123
|
+
await generatedAnswer.clickToggleButton();
|
|
124
|
+
await hideAnswerAnalyticRequestPromise;
|
|
125
|
+
|
|
126
|
+
const showAnswerAnalyticRequestPromise =
|
|
127
|
+
generatedAnswer.waitForShowAnswersUaAnalytics();
|
|
128
|
+
await generatedAnswer.clickToggleButton();
|
|
129
|
+
await showAnswerAnalyticRequestPromise;
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test.describe('when interacting with citations', () => {
|
|
134
|
+
test.describe('when hovering over a citation', () => {
|
|
135
|
+
test('should log citation hover analytics', async ({
|
|
136
|
+
generatedAnswer,
|
|
137
|
+
}) => {
|
|
138
|
+
const citationIndex = 0;
|
|
139
|
+
const {id, permanentid} = genQaData.citations[citationIndex];
|
|
140
|
+
const citationHoverAnalyticRequestPromise =
|
|
141
|
+
generatedAnswer.waitForSourceHoverUaAnalytics({
|
|
142
|
+
citationId: id,
|
|
143
|
+
permanentId: permanentid,
|
|
144
|
+
});
|
|
145
|
+
await generatedAnswer.hoverOverCitation(citationIndex);
|
|
146
|
+
await citationHoverAnalyticRequestPromise;
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test.describe('when clicking on a citation', () => {
|
|
151
|
+
test('should log citation click analytics', async ({
|
|
152
|
+
generatedAnswer,
|
|
153
|
+
}) => {
|
|
154
|
+
const citationIndex = 0;
|
|
155
|
+
const {id, title, source, uri, clickUri, permanentid} =
|
|
156
|
+
genQaData.citations[citationIndex];
|
|
157
|
+
const citationClickAnalyticRequestPromise =
|
|
158
|
+
generatedAnswer.waitForCitationClickUaAnalytics(
|
|
159
|
+
{
|
|
160
|
+
documentTitle: title,
|
|
161
|
+
sourceName: source,
|
|
162
|
+
documentPosition: citationIndex + 1,
|
|
163
|
+
documentUri: uri,
|
|
164
|
+
documentUrl: clickUri,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
citationId: id,
|
|
168
|
+
contentIDKey: 'permanentid',
|
|
169
|
+
contentIDValue: permanentid,
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
await generatedAnswer.clickOnCitation(citationIndex);
|
|
173
|
+
await citationClickAnalyticRequestPromise;
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template lwc:if={shouldDisplayGeneratedAnswer}>
|
|
3
|
-
<div data-
|
|
3
|
+
<div data-testid="generated-answer__card" class="slds-box">
|
|
4
4
|
<div class="generated-answer__card-header slds-grid">
|
|
5
5
|
<span
|
|
6
|
+
data-testid="generated-answer__badge"
|
|
6
7
|
class="generated-answer__badge slds-var-p-around_x-small slds-text-title_bold"
|
|
7
8
|
>{labels.generatedAnswerForYou}</span
|
|
8
9
|
>
|
|
9
10
|
<div class="generated-answer__header-actions slds-grid">
|
|
10
11
|
<template lwc:if={shouldDisplayActions}>
|
|
11
|
-
<div
|
|
12
|
+
<div
|
|
13
|
+
data-testid="generated-answer__actions"
|
|
14
|
+
class="slds-grid flex-one slds-grid_align-end"
|
|
15
|
+
>
|
|
12
16
|
<c-quantic-feedback
|
|
13
17
|
state={feedbackState}
|
|
14
18
|
onquantic__like={handleLike}
|
|
@@ -24,7 +28,7 @@
|
|
|
24
28
|
data-cy="generated-answer__feedback"
|
|
25
29
|
></c-quantic-feedback>
|
|
26
30
|
<c-quantic-generated-answer-copy-to-clipboard
|
|
27
|
-
data-
|
|
31
|
+
data-testid="generated-answer__copy-to-clipboard"
|
|
28
32
|
answer={answer}
|
|
29
33
|
class="slds-var-m-horizontal_xx-small"
|
|
30
34
|
></c-quantic-generated-answer-copy-to-clipboard>
|
|
@@ -49,34 +53,44 @@
|
|
|
49
53
|
answer-content-format={answerContentFormat}
|
|
50
54
|
answer={answer}
|
|
51
55
|
is-streaming={isStreaming}
|
|
52
|
-
data-cy="generated-answer__content"
|
|
56
|
+
data-cy="generated-answer__content"
|
|
57
|
+
>
|
|
53
58
|
</c-quantic-generated-answer-content>
|
|
54
59
|
</div>
|
|
55
60
|
<div
|
|
56
61
|
data-cy="generated-answer__footer"
|
|
57
62
|
class={generatedAnswerFooterCssClass}
|
|
58
63
|
>
|
|
59
|
-
<div
|
|
64
|
+
<div
|
|
65
|
+
class={generatedAnswerFooterRowClass}
|
|
66
|
+
data-cy="generated-answer__footer-row"
|
|
67
|
+
>
|
|
60
68
|
<template lwc:if={shouldDisplayCitations}>
|
|
61
69
|
<div class="slds-var-m-top_small">
|
|
62
70
|
<c-quantic-source-citations
|
|
63
71
|
engine-id={engineId}
|
|
64
|
-
data-
|
|
72
|
+
data-testid="generated-answer__citations"
|
|
65
73
|
citations={citations}
|
|
66
74
|
citation-hover-handler={handleCitationHover}
|
|
67
75
|
></c-quantic-source-citations>
|
|
68
76
|
</div>
|
|
69
77
|
</template>
|
|
70
78
|
</div>
|
|
71
|
-
<div
|
|
79
|
+
<div
|
|
80
|
+
class={generatedAnswerFooterRowClass}
|
|
81
|
+
data-cy="generated-answer__footer-row"
|
|
82
|
+
>
|
|
72
83
|
<template lwc:if={shouldShowCollapseGeneratingMessage}>
|
|
73
|
-
<div
|
|
84
|
+
<div
|
|
85
|
+
data-testid="generated-answer__collapse-generating-message"
|
|
86
|
+
class="generated-answer__collapse-generating-message"
|
|
87
|
+
>
|
|
74
88
|
{labels.generatingAnswer}...
|
|
75
89
|
</div>
|
|
76
90
|
</template>
|
|
77
91
|
<template lwc:if={shouldShowToggleCollapseAnswer}>
|
|
78
92
|
<lightning-button
|
|
79
|
-
data-
|
|
93
|
+
data-testid="generated-answer__answer-toggle"
|
|
80
94
|
icon-name={toggleCollapseAnswerIcon}
|
|
81
95
|
variant="base"
|
|
82
96
|
label={toggleCollapseAnswerLabel}
|
|
@@ -89,7 +103,7 @@
|
|
|
89
103
|
<template lwc:if={shouldShowDisclaimer}>
|
|
90
104
|
<div
|
|
91
105
|
class="generated-answer__disclaimer slds-grid_vertical-align-center slds-col slds-text-color_weak slds-text-body_small"
|
|
92
|
-
data-
|
|
106
|
+
data-testid="generated-answer__disclaimer"
|
|
93
107
|
>
|
|
94
108
|
<slot name="disclaimer"> {labels.rgaDisclaimer} </slot>
|
|
95
109
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template lwc:if={shouldDisplayGeneratedAnswer}>
|
|
3
3
|
<div
|
|
4
|
-
data-
|
|
4
|
+
data-testid="generated-answer__card"
|
|
5
5
|
class="slds-box"
|
|
6
6
|
>
|
|
7
7
|
<div
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<div>{labels.couldNotGenerateAnAnswer}</div>
|
|
16
16
|
<div class="slds-var-m-top_small">
|
|
17
17
|
<lightning-button
|
|
18
|
-
data-
|
|
18
|
+
data-testid="generated-answer__retry-button"
|
|
19
19
|
variant="brand-outline"
|
|
20
20
|
label={labels.tryAgain}
|
|
21
21
|
title={labels.tryAgain}
|
package/force-app/main/default/lwc/quanticGeneratedAnswerToggle/quanticGeneratedAnswerToggle.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="slds-is-relative" onmouseenter={handleMouseEnter}
|
|
3
3
|
onmouseleave={handleMouseLeave}>
|
|
4
4
|
<lightning-input
|
|
5
|
-
data-
|
|
5
|
+
data-testid="generated-answer__toggle-button"
|
|
6
6
|
type="toggle"
|
|
7
7
|
onchange={toggleGeneratedAnswer}
|
|
8
8
|
checked={isGeneratedAnswerVisible}
|