Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(atomic): added Generated Answer component #3003

Merged
merged 106 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 99 commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
4333b7e
feat(headless): added generative answer feature
nathanlb May 19, 2023
b1576cf
feat(headless): added slice tests
nathanlb May 23, 2023
09a6ee1
feat(headless): added client
nathanlb May 25, 2023
c679679
feat(headless): subscribe
nathanlb May 29, 2023
64a0c3a
feat(headless): temp retry logic
nathanlb May 30, 2023
79fd5fa
feat(headless): merged with master
nathanlb May 31, 2023
868a8d8
feat(headless): client retry
nathanlb Jun 2, 2023
3ff8b32
feat(headless): exports
nathanlb Jun 2, 2023
b3eefba
feat(headless): added error codes
nathanlb Jun 5, 2023
ef8f6dd
feat(atomic): prelim components
nathanlb Jun 5, 2023
b4eb092
feat(atomic): tag fix
nathanlb Jun 6, 2023
6d15715
feat(headless): actually building a controller lol
nathanlb Jun 6, 2023
1168e82
feat(headless): actually building a controller lol
nathanlb Jun 6, 2023
c2d8713
feat(headless): id and error handling tweaks
nathanlb Jun 12, 2023
95185b7
feat(atomic): merge with headless branch
nathanlb Jun 12, 2023
7c82cb8
feat(headless): added stream id to search event custom data
nathanlb Jun 13, 2023
1ff51a0
feat(atomic): merge with headless branch
nathanlb Jun 13, 2023
ca66eb0
feat(headless): complete event might contain delta
nathanlb Jun 14, 2023
4d151e7
feat(headless): complete event might contain delta
nathanlb Jun 14, 2023
af560ed
feat(atomic): merge with headless branch
nathanlb Jun 14, 2023
8ee3a08
fix(headless): started adding payload schemas
nathanlb Jun 14, 2023
1799cd7
feat(atomic): merge with headless branch
nathanlb Jun 14, 2023
2d0185f
feat(genqa): enum rename
nathanlb Jun 15, 2023
7680666
feat(headless): merged with master
nathanlb Jun 15, 2023
b02ce2e
feat(atomic): merge with headless branch
nathanlb Jun 15, 2023
4d7edc5
feat(atomic): added feedback buttons
nathanlb Jun 16, 2023
807ac8a
feat(headless): started adding analytics actions
nathanlb Jun 16, 2023
8abb666
feat(atomic): merge with headless branch
nathanlb Jun 16, 2023
2d38196
feat(atomic): added citations
nathanlb Jun 16, 2023
c1f5417
feat(headless): citatio interface export
nathanlb Jun 16, 2023
63aed5c
feat(atomic): merge with headless branch
nathanlb Jun 16, 2023
63e65d5
feat(atomic): added localized strings
nathanlb Jun 19, 2023
d26a225
feat(headless): punctuation
nathanlb Jun 19, 2023
c8f0340
feat(atomic): merge with headless branch
nathanlb Jun 19, 2023
9ee114d
feat(atomic): show when loading
nathanlb Jun 19, 2023
e3a4666
feat(headless): new citations schema
nathanlb Jun 19, 2023
c1550f6
feat(atomic): merge with parent branch
nathanlb Jun 19, 2023
53a5436
feat(atomic): no score
nathanlb Jun 19, 2023
316a665
feat(headless): removed score
nathanlb Jun 19, 2023
b5fb05d
feat(atomic): merge with headless branch
nathanlb Jun 19, 2023
1e1ebb4
feat(atomic): updated loader
nathanlb Jun 19, 2023
a70be9a
feat(headless): removed oncomplete
nathanlb Jun 20, 2023
89c838b
feat(atomic): merge with headless branch
nathanlb Jun 20, 2023
b2531a7
feat(headless): added retryable state flag
nathanlb Jun 21, 2023
f4046fd
feat(atomic): used analytics actions
nathanlb Jun 21, 2023
75e510f
feat(atomic): merge with headless branch
nathanlb Jun 21, 2023
ece9504
feat(atomic): fixed loading transition
nathanlb Jun 21, 2023
b1f1bfd
feat(headless): merged with master
nathanlb Jun 21, 2023
b702083
feat(headless): tests for setIsLoading
nathanlb Jun 21, 2023
7c65012
feat(atomic): merged with parent
nathanlb Jun 21, 2023
9f96fe3
feat(atomic): citation max width
nathanlb Jun 21, 2023
e4926d7
feat(headless): added more controller tests
nathanlb Jun 21, 2023
0eb9b3f
feat(headless): merged with master
nathanlb Jun 21, 2023
b409965
feat(headless): removed unused analytics util
nathanlb Jun 22, 2023
35df6f6
feat(atomic): merge with headless branch
nathanlb Jun 22, 2023
b08467f
feat(headless): moved handlers to asyncthunk
nathanlb Jun 22, 2023
a1cf0da
feat(atomic): merge with headless branch
nathanlb Jun 22, 2023
3205c88
feat(headless): string literal
nathanlb Jun 22, 2023
003066f
feat(atomic): merge with headless branch
nathanlb Jun 22, 2023
06389f6
feat(atomic): added genqa example page
nathanlb Jun 22, 2023
3df616d
feat(headless): added like and dislike to the state
nathanlb Jun 22, 2023
b38be5e
feat(headless): error payload type
nathanlb Jun 22, 2023
5636a2b
feat(headless): renamed and added citation seletor
nathanlb Jun 22, 2023
5cb0417
feat(atomic): merge with headless branch
nathanlb Jun 22, 2023
792f439
feat(atomic): feedback button state
nathanlb Jun 22, 2023
949ef7e
feat(headless): switched libs
nathanlb Jun 22, 2023
701cfa9
feat(headless): reset error state
nathanlb Jun 23, 2023
1f30ffb
feat(atomic): merged with parent
nathanlb Jun 23, 2023
294531a
feat(atomic): feedback button dimensions
nathanlb Jun 23, 2023
22a41fe
feat(headless): controller tests
nathanlb Jun 23, 2023
333463a
feat(headless): changed handlers for action injection
nathanlb Jun 24, 2023
a4ad27d
feat(headless): controller tests
nathanlb Jun 23, 2023
5be690e
feat(headless): changed handlers for action injection
nathanlb Jun 24, 2023
12275d6
feat(headless): onpopen handler
nathanlb Jun 26, 2023
c02d48a
feat(atomic): merged with parent
nathanlb Jun 26, 2023
627deee
feat(atomic): added retry button
nathanlb Jun 26, 2023
657f34a
feat(atomic): started tests
nathanlb Jun 26, 2023
79336d0
feat(headless): added arguments to fatalerror
nathanlb Jun 26, 2023
1724b4e
feat(atomic): merge with headless branch
nathanlb Jun 26, 2023
cba17a4
feat(atomic): added tests
nathanlb Jun 27, 2023
99754b0
feat(headless): pr feedback
nathanlb Jun 27, 2023
3fa5f6c
feat(atomic): merge with headless branch
nathanlb Jun 27, 2023
24ba903
feat(atomic): added tests
nathanlb Jun 27, 2023
18d7d54
feat(headless): changed client callbacks
nathanlb Jun 27, 2023
5cd5412
feat(atomic): added tests
nathanlb Jun 27, 2023
7c469ca
feat(headless): switch it up
nathanlb Jun 27, 2023
0169c9c
feat(atomic): merge with headless branch
nathanlb Jun 27, 2023
5463252
feat(headless): merged with master
nathanlb Jun 27, 2023
837d357
feat(headless): changed accept header
nathanlb Jun 27, 2023
eedea1a
feat(atomic): merge with headless branch
nathanlb Jun 27, 2023
1e00eb4
feat(atomic): added tests
nathanlb Jun 27, 2023
fab1d06
feat(atomic): added retry test
nathanlb Jun 27, 2023
c4967c7
feat(headless): setting error before throw from onerror
nathanlb Jun 27, 2023
0712af7
feat(atomic): merge with headless branch
nathanlb Jun 27, 2023
f185fae
feat(headless): wrong error
nathanlb Jun 27, 2023
8d03171
feat(atomic): merge with headless branch
nathanlb Jun 27, 2023
64a722f
feat(atomic): retry button tests
nathanlb Jun 27, 2023
ca3ffb9
feat(atomic): unknown instead of any
nathanlb Jun 27, 2023
8c8dd76
feat(atomic): merged with master
nathanlb Jun 27, 2023
3f2d8e3
feat(atomic): title to citation links
nathanlb Jun 27, 2023
4636dd4
feat(atomic): pr feedback
nathanlb Jun 27, 2023
2b90e02
feat(atomic): css and a11y feedback implemented
nathanlb Jun 27, 2023
f77f5a6
feat(atomic): props interface
nathanlb Jun 27, 2023
54dd15e
feat(atomic): removed smart snippets from genqa example
nathanlb Jun 27, 2023
b111d03
feat(atomic): ordered list
nathanlb Jun 28, 2023
2a665c0
feat(atomic): key on top level element
nathanlb Jun 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions packages/atomic/cypress/e2e/generated-answer-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {RouteAlias} from '../fixtures/fixture-common';
import {TestFixture, generateComponentHTML} from '../fixtures/test-fixture';

export const getStreamInterceptAlias = (streamId: string) =>
`${RouteAlias.GenQAStream}-${streamId}`;

export function mockStreamResponse(streamId: string, body: unknown) {
cy.intercept(
{
method: 'GET',
url: `**/machinelearning/streaming/${streamId}`,
},
(request) => {
request.reply(200, `data: ${JSON.stringify(body)} \n\n`, {
'content-type': 'text/event-stream',
});
}
).as(getStreamInterceptAlias(streamId).substring(1));
}

export function mockStreamError(streamId: string, errorCode: number) {
cy.intercept(
{
method: 'GET',
url: `**/machinelearning/streaming/${streamId}`,
},
(request) => {
request.reply(
errorCode,
{},
{
'content-type': 'text/event-stream',
}
);
}
).as(getStreamInterceptAlias(streamId).substring(1));
}

export const addGeneratedAnswer =
(streamId?: string) => (fixture: TestFixture) => {
const element = generateComponentHTML('atomic-generated-answer');
fixture.withElement(element).withCustomResponse((response) => {
if (streamId) {
response.extendedResults = {
generativeQuestionAnsweringId: streamId,
};
}
});
};
26 changes: 26 additions & 0 deletions packages/atomic/cypress/e2e/generated-answer-selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const generatedAnswerComponent = 'atomic-generated-answer';
export const GeneratedAnswerSelectors = {
shadow: () => cy.get(generatedAnswerComponent).shadow(),
container: () => GeneratedAnswerSelectors.shadow().find('[part="container"]'),
content: () =>
GeneratedAnswerSelectors.shadow().find('[part="generated-content"]'),
answer: () =>
GeneratedAnswerSelectors.shadow().find('[part="generated-text"]'),
headerLabel: () =>
GeneratedAnswerSelectors.shadow().find('[part="header-label"]'),
likeButton: () =>
GeneratedAnswerSelectors.shadow().find('.feedback-button.like'),
dislikeButton: () =>
GeneratedAnswerSelectors.shadow().find('.feedback-button.dislike'),
citation: () => GeneratedAnswerSelectors.shadow().find('.citation'),
citationsLabel: () =>
GeneratedAnswerSelectors.shadow().find('.citations-label'),
citationTitle: () =>
GeneratedAnswerSelectors.citation().find('.citation-title'),
citationIndex: () =>
GeneratedAnswerSelectors.citation().find('.citation-index'),
loader: () => GeneratedAnswerSelectors.shadow().find('.typing-indicator'),
retryContainer: () =>
GeneratedAnswerSelectors.shadow().find('[part="retry-container"]'),
retryButton: () => GeneratedAnswerSelectors.retryContainer().find('button'),
};
187 changes: 187 additions & 0 deletions packages/atomic/cypress/e2e/generated-answer.cypress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import {TestFixture} from '../fixtures/test-fixture';
import {
addGeneratedAnswer,
getStreamInterceptAlias,
mockStreamError,
mockStreamResponse,
} from './generated-answer-actions';
import {GeneratedAnswerSelectors} from './generated-answer-selectors';

describe('Generated Answer Test Suites', () => {
describe('Generated Answer', () => {
function setupGeneratedAnswer(streamId?: string) {
new TestFixture().with(addGeneratedAnswer(streamId)).init();
}

describe('when no stream ID is returned', () => {
beforeEach(() => {
setupGeneratedAnswer();
});

it('should not display the component', () => {
GeneratedAnswerSelectors.container().should('not.exist');
});
});

describe('when a stream ID is returned', () => {
describe('when a message event is received', () => {
const streamId = crypto.randomUUID();

const testTextDelta = 'Some text';
const testMessagePayload = {
payloadType: 'genqa.messageType',
payload: JSON.stringify({
textDelta: testTextDelta,
}),
finishReason: 'COMPLETED',
};

beforeEach(() => {
mockStreamResponse(streamId, testMessagePayload);
setupGeneratedAnswer(streamId);
});

it('should log the stream ID in the search event custom data', () => {
TestFixture.getUACustomData().then((customData) => {
expect(customData).to.have.property(
'generativeQuestionAnsweringId',
streamId
);
});
});

it('should display the message', () => {
cy.wait(getStreamInterceptAlias(streamId));

GeneratedAnswerSelectors.answer().should('have.text', testTextDelta);
});

it('should display feedback buttons', () => {
cy.wait(getStreamInterceptAlias(streamId));

GeneratedAnswerSelectors.likeButton().should('exist');
GeneratedAnswerSelectors.dislikeButton().should('exist');
});
});

describe('when a citation event is received', () => {
const streamId = crypto.randomUUID();

const testCitation = {
id: 'some-id-123',
title: 'Some Title',
uri: 'https://www.coveo.com',
permanentid: 'some-permanent-id-123',
clickUri: 'https://www.coveo.com/en',
};
const testMessagePayload = {
payloadType: 'genqa.citationsType',
payload: JSON.stringify({
citations: [testCitation],
}),
finishReason: 'COMPLETED',
};

beforeEach(() => {
mockStreamResponse(streamId, testMessagePayload);
setupGeneratedAnswer(streamId);
});

it('should display the citation link', () => {
cy.wait(getStreamInterceptAlias(streamId));

GeneratedAnswerSelectors.citationsLabel().should(
'have.text',
'Learn more'
);
GeneratedAnswerSelectors.citationTitle().should(
'have.text',
testCitation.title
);
GeneratedAnswerSelectors.citationIndex().should('have.text', '1');
GeneratedAnswerSelectors.citation().should(
'have.attr',
'href',
testCitation.clickUri
);
});
});

describe('when an error event is received', () => {
const streamId = crypto.randomUUID();

const testErrorPayload = {
finishReason: 'ERROR',
errorMessage: 'An error message',
errorCode: 500,
};

beforeEach(() => {
mockStreamResponse(streamId, testErrorPayload);
setupGeneratedAnswer(streamId);
});

it('should not display the component', () => {
cy.wait(getStreamInterceptAlias(streamId));

GeneratedAnswerSelectors.container().should('not.exist');
});
});

describe('when the stream connection fails', () => {
const streamId = crypto.randomUUID();

describe('Non-retryable error (4XX)', () => {
beforeEach(() => {
mockStreamError(streamId, 406);
setupGeneratedAnswer(streamId);
});

it('should not show the component', () => {
cy.wait(getStreamInterceptAlias(streamId));

GeneratedAnswerSelectors.container().should('not.exist');
});
});

describe('Retryable error', () => {
[500, 429].forEach((errorCode) => {
describe(`${errorCode} error`, () => {
beforeEach(() => {
Cypress.on('uncaught:exception', () => false);
mockStreamError(streamId, 500);
setupGeneratedAnswer(streamId);
});

it('should retry the stream 3 times then offer a retry button', () => {
cy.wait(getStreamInterceptAlias(streamId));

GeneratedAnswerSelectors.container().should('not.exist');

cy.wait(getStreamInterceptAlias(streamId));

GeneratedAnswerSelectors.container().should('not.exist');

cy.wait(getStreamInterceptAlias(streamId));

GeneratedAnswerSelectors.container().should('not.exist');

cy.wait(getStreamInterceptAlias(streamId));

const retryAlias = '@retrySearch';
cy.intercept({
method: 'POST',
url: '**/rest/search/v2?*',
}).as(retryAlias.substring(1));

GeneratedAnswerSelectors.retryButton().click();

cy.wait(retryAlias);
});
});
});
});
});
});
});
});
1 change: 1 addition & 0 deletions packages/atomic/cypress/fixtures/fixture-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const RouteAlias = {
FacetSearch: '@coveoFacetSearch',
Quickview: '@coveoQuickview',
Locale: '@locale',
GenQAStream: '@genQAStream',
};

export const ConsoleAliases = {
Expand Down
13 changes: 13 additions & 0 deletions packages/atomic/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ export namespace Components {
}
interface AtomicFrequentlyBoughtTogether {
}
interface AtomicGeneratedAnswer {
}
interface AtomicHtml {
/**
* Specify if the content should be sanitized, using [`DOMPurify`](https://www.npmjs.com/package/dompurify).
Expand Down Expand Up @@ -2124,6 +2126,12 @@ declare global {
prototype: HTMLAtomicFrequentlyBoughtTogetherElement;
new (): HTMLAtomicFrequentlyBoughtTogetherElement;
};
interface HTMLAtomicGeneratedAnswerElement extends Components.AtomicGeneratedAnswer, HTMLStencilElement {
}
var HTMLAtomicGeneratedAnswerElement: {
prototype: HTMLAtomicGeneratedAnswerElement;
new (): HTMLAtomicGeneratedAnswerElement;
};
interface HTMLAtomicHtmlElement extends Components.AtomicHtml, HTMLStencilElement {
}
var HTMLAtomicHtmlElement: {
Expand Down Expand Up @@ -2822,6 +2830,7 @@ declare global {
"atomic-format-number": HTMLAtomicFormatNumberElement;
"atomic-format-unit": HTMLAtomicFormatUnitElement;
"atomic-frequently-bought-together": HTMLAtomicFrequentlyBoughtTogetherElement;
"atomic-generated-answer": HTMLAtomicGeneratedAnswerElement;
"atomic-html": HTMLAtomicHtmlElement;
"atomic-icon": HTMLAtomicIconElement;
"atomic-insight-edit-toggle": HTMLAtomicInsightEditToggleElement;
Expand Down Expand Up @@ -3253,6 +3262,8 @@ declare namespace LocalJSX {
}
interface AtomicFrequentlyBoughtTogether {
}
interface AtomicGeneratedAnswer {
}
interface AtomicHtml {
/**
* Specify if the content should be sanitized, using [`DOMPurify`](https://www.npmjs.com/package/dompurify).
Expand Down Expand Up @@ -4779,6 +4790,7 @@ declare namespace LocalJSX {
"atomic-format-number": AtomicFormatNumber;
"atomic-format-unit": AtomicFormatUnit;
"atomic-frequently-bought-together": AtomicFrequentlyBoughtTogether;
"atomic-generated-answer": AtomicGeneratedAnswer;
"atomic-html": AtomicHtml;
"atomic-icon": AtomicIcon;
"atomic-insight-edit-toggle": AtomicInsightEditToggle;
Expand Down Expand Up @@ -4917,6 +4929,7 @@ declare module "@stencil/core" {
"atomic-format-number": LocalJSX.AtomicFormatNumber & JSXBase.HTMLAttributes<HTMLAtomicFormatNumberElement>;
"atomic-format-unit": LocalJSX.AtomicFormatUnit & JSXBase.HTMLAttributes<HTMLAtomicFormatUnitElement>;
"atomic-frequently-bought-together": LocalJSX.AtomicFrequentlyBoughtTogether & JSXBase.HTMLAttributes<HTMLAtomicFrequentlyBoughtTogetherElement>;
"atomic-generated-answer": LocalJSX.AtomicGeneratedAnswer & JSXBase.HTMLAttributes<HTMLAtomicGeneratedAnswerElement>;
"atomic-html": LocalJSX.AtomicHtml & JSXBase.HTMLAttributes<HTMLAtomicHtmlElement>;
"atomic-icon": LocalJSX.AtomicIcon & JSXBase.HTMLAttributes<HTMLAtomicIconElement>;
"atomic-insight-edit-toggle": LocalJSX.AtomicInsightEditToggle & JSXBase.HTMLAttributes<HTMLAtomicInsightEditToggleElement>;
Expand Down
Loading