Skip to content

Commit

Permalink
FTR for playground (#186246)
Browse files Browse the repository at this point in the history
## Summary

This PR contains high-priority FTR tests for the playground in ESS.


### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)


### Risk Matrix

Delete this section if it is not applicable to this PR.

Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.

When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:

| Risk | Probability | Severity | Mitigation/Notes |

|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces—unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes—Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
Samiul-TheSoccerFan and kibanamachine authored Jun 27, 2024
1 parent 5178489 commit 5324dc2
Show file tree
Hide file tree
Showing 13 changed files with 699 additions and 205 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/search_playground/public/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const Chat = () => {
iconType="sparkles"
disabled={isToolBarActionsDisabled}
onClick={regenerateMessages}
data-test-subj="regenerateActionButton"
>
<FormattedMessage
id="xpack.searchPlayground.chat.regenerateBtn"
Expand All @@ -169,6 +170,7 @@ export const Chat = () => {
iconType="refresh"
disabled={isToolBarActionsDisabled}
onClick={handleClearChat}
data-test-subj="clearChatActionButton"
>
<FormattedMessage
id="xpack.searchPlayground.chat.clearChatBtn"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const ChatSidebar: React.FC<ChatSidebarProps> = ({ selectedIndicesCount }
defaultMessage: 'Model settings',
}),
children: <SummarizationPanel />,
dataTestId: 'summarizationAccordion',
},
{
id: useGeneratedHtmlId({ prefix: 'sourcesAccordion' }),
Expand All @@ -50,13 +51,14 @@ export const ChatSidebar: React.FC<ChatSidebarProps> = ({ selectedIndicesCount }
</EuiText>
),
children: <SourcesPanelSidebar />,
dataTestId: 'sourcesAccordion',
},
];
const [openAccordionId, setOpenAccordionId] = useState(accordions[0].id);

return (
<EuiFlexGroup direction="column" className="eui-yScroll" gutterSize="none">
{accordions.map(({ id, title, extraAction, children }, index) => (
{accordions.map(({ id, title, extraAction, children, dataTestId }, index) => (
<EuiFlexItem
key={id}
css={{
Expand All @@ -77,6 +79,7 @@ export const ChatSidebar: React.FC<ChatSidebarProps> = ({ selectedIndicesCount }
buttonProps={{ paddingSize: 'l' }}
forceState={openAccordionId === id ? 'open' : 'closed'}
onToggle={() => setOpenAccordionId(openAccordionId === id ? '' : id)}
data-test-subj={dataTestId}
>
{children}
<EuiSpacer size="l" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const IndicesList: React.FC<IndicesListProps> = ({ indices, onRemoveClick
color="primary"
label={index}
size="s"
data-test-subj="indicesInAccordian"
extraAction={{
alwaysShow: true,
'aria-label': i18n.translate('xpack.searchPlayground.sources.indices.removeIndex', {
Expand Down
166 changes: 131 additions & 35 deletions x-pack/test/functional/apps/search_playground/playground_overview.ess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@
* 2.0.
*/

import type OpenAI from 'openai';
import { FtrProviderContext } from '../../ftr_provider_context';
import { createOpenAIConnector } from './utils/create_openai_connector';
import { MachineLearningCommonAPIProvider } from '../../services/ml/common_api';

import {
createLlmProxy,
LlmProxy,
} from '../../../observability_ai_assistant_api_integration/common/create_llm_proxy';

const indexName = 'basic_index';
const esArchiveIndex = 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index';

Expand All @@ -18,76 +24,166 @@ export default function (ftrContext: FtrProviderContext) {
const commonAPI = MachineLearningCommonAPIProvider(ftrContext);
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');

const log = getService('log');
const browser = getService('browser');

const createIndex = async () => await esArchiver.load(esArchiveIndex);

let proxy: LlmProxy;
let removeOpenAIConnector: () => Promise<void>;
const createConnector = async () => {
removeOpenAIConnector = await createOpenAIConnector({
supertest,
requestHeader: commonAPI.getCommonRequestHeader(),
proxy,
});
};

describe('Playground Overview', () => {
describe('Playground', () => {
before(async () => {
proxy = await createLlmProxy(log);
await pageObjects.common.navigateToApp('enterpriseSearchApplications/playground');
});

after(async () => {
await esArchiver.unload(esArchiveIndex);
await removeOpenAIConnector?.();
proxy.close();
});

describe('start chat page', () => {
it('playground app is loaded', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundStartChatPageComponentsToExist();
describe('setup Page', () => {
it('is loaded successfully', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundHeaderComponentsToExist();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundHeaderComponentsToDisabled();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundStartChatPageComponentsToExist();
});

it('show no index callout', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectNoIndexCalloutExists();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectCreateIndexButtonToExists();
});

it('hide no index callout when index added', async () => {
await createIndex();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectSelectIndex(indexName);
});

it('show add connector button', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectAddConnectorButtonExists();
describe('with gen ai connectors', () => {
before(async () => {
await createConnector();
await browser.refresh();
});

after(async () => {
await removeOpenAIConnector?.();
await browser.refresh();
});
it('hide gen ai panel', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectHideGenAIPanelConnector();
});
});

it('click add connector button opens connector flyout', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectOpenConnectorPagePlayground();
describe('without gen ai connectors', () => {
it('should display the set up connectors button', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectAddConnectorButtonExists();
});

it('creates a connector successfully', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectOpenConnectorPagePlayground();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectHideGenAIPanelConnectorAfterCreatingConnector(
createConnector
);
});

after(async () => {
await removeOpenAIConnector?.();
await browser.refresh();
});
});

it('hide gen ai panel when connector exists', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectHideGenAIPanelConnector(
createConnector
);
describe('without any indices', () => {
it('show no index callout', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectNoIndexCalloutExists();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectCreateIndexButtonToExists();
});

it('hide no index callout when index added', async () => {
await createIndex();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectSelectIndex(indexName);
});

after(async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.removeIndexFromComboBox();
await esArchiver.unload(esArchiveIndex);
await browser.refresh();
});
});

it('show chat page', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectSelectIndex(indexName);
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectToStartChatPage();
describe('with existing indices', () => {
before(async () => {
await createConnector();
await createIndex();
await browser.refresh();
});

it('dropdown shows up', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectIndicesInDropdown();
});

it('can select index from dropdown and navigate to chat window', async () => {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectToSelectIndicesAndStartButtonEnabled(
indexName
);
});

after(async () => {
await removeOpenAIConnector?.();
await esArchiver.unload(esArchiveIndex);
await browser.refresh();
});
});
});

describe('chat page', () => {
it('chat works', async () => {
await pageObjects.searchPlayground.PlaygroundChatPage.expectChatWorks();
before(async () => {
await createConnector();
await createIndex();
await browser.refresh();
await pageObjects.searchPlayground.PlaygroundChatPage.navigateToChatPage(indexName);
});

it('open view code', async () => {
await pageObjects.searchPlayground.PlaygroundChatPage.expectOpenViewCode();
it('loads successfully', async () => {
await pageObjects.searchPlayground.PlaygroundChatPage.expectChatWindowLoaded();
});

it('show fields and code in view query', async () => {
await pageObjects.searchPlayground.PlaygroundChatPage.expectViewQueryHasFields();
describe('chat', () => {
it('works', async () => {
const conversationInterceptor = proxy.intercept(
'conversation',
(body) =>
(JSON.parse(body) as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming).tools?.find(
(fn) => fn.function.name === 'title_conversation'
) === undefined
);

await pageObjects.searchPlayground.PlaygroundChatPage.sendQuestion();

const conversationSimulator = await conversationInterceptor.waitForIntercept();

await conversationSimulator.next('My response');

await conversationSimulator.complete();

await pageObjects.searchPlayground.PlaygroundChatPage.expectChatWorks();
await pageObjects.searchPlayground.PlaygroundChatPage.expectTokenTooltipExists();
});

it('open view code', async () => {
await pageObjects.searchPlayground.PlaygroundChatPage.expectOpenViewCode();
});

it('show fields and code in view query', async () => {
await pageObjects.searchPlayground.PlaygroundChatPage.expectViewQueryHasFields();
});

it('show edit context', async () => {
await pageObjects.searchPlayground.PlaygroundChatPage.expectEditContextOpens();
});
});

it('show edit context', async () => {
await pageObjects.searchPlayground.PlaygroundChatPage.expectEditContextOpens();
after(async () => {
await removeOpenAIConnector?.();
await esArchiver.unload(esArchiveIndex);
await browser.refresh();
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@
*/

import type SuperTest from 'supertest';
import { LlmProxy } from '../../../../observability_ai_assistant_api_integration/common/create_llm_proxy';

export async function createOpenAIConnector({
supertest,
requestHeader = {},
apiKeyHeader = {},
proxy,
}: {
supertest: SuperTest.Agent;
requestHeader?: Record<string, string>;
apiKeyHeader?: Record<string, string>;
proxy: LlmProxy;
}): Promise<() => Promise<void>> {
const config = {
apiProvider: 'OpenAI',
defaultModel: 'gpt-4',
apiUrl: 'http://localhost:3002',
apiUrl: `http://localhost:${proxy.getPort()}`,
};

const connector: { id: string } | undefined = (
Expand All @@ -28,7 +31,7 @@ export async function createOpenAIConnector({
.set(requestHeader)
.set(apiKeyHeader)
.send({
name: 'test Open AI',
name: 'myConnector',
connector_type_id: '.gen-ai',
config,
secrets: {
Expand Down
Loading

0 comments on commit 5324dc2

Please sign in to comment.