[8.14] [Playground] Propagate Error message into FE (#182201) (#182250)

# Backport

This will backport the following commits from `main` to `8.14`:
- [[Playground] Propagate Error message into FE
(#182201)](https://github.com/elastic/kibana/pull/182201)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Samiul
Monir","email":"150824886+Samiul-TheSoccerFan@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-05-01T14:22:41Z","message":"[Playground]
Propagate Error message into FE (#182201)\n\n## Summary\r\n\r\n- Fix
error not being propagated into FE\r\n- Added tests\r\n\r\n##
UI\r\n\r\n### Rate limit error message:\r\n![Screenshot 2024-04-30 at 1
05\r\n40 PM](2734d27b-ef2b-469b-9344-a7c62cd502bc)\r\n\r\n###
BAD LLM\r\n![Screenshot 2024-04-30 at 1
05\r\n47 PM](fb3025e5-5f5e-49e6-8277-7dba52cc76cd)\r\n\r\n###
Invalid API key\r\n![Screenshot 2024-04-30 at 1
06\r\n26 PM](67c1bdf6-6785-48ed-870a-ae24e8ac7e9f)\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[ ] If a plugin configuration key changed, check if it needs to
be\r\nallowlisted in the cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[ ] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"0288bb466403452dad0fe0958da9a2249a473072","branchLabelMapping":{"^v8.15.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:EnterpriseSearch","v8.14.0","v8.15.0"],"number":182201,"url":"https://github.com/elastic/kibana/pull/182201","mergeCommit":{"message":"[Playground]
Propagate Error message into FE (#182201)\n\n## Summary\r\n\r\n- Fix
error not being propagated into FE\r\n- Added tests\r\n\r\n##
UI\r\n\r\n### Rate limit error message:\r\n![Screenshot 2024-04-30 at 1
05\r\n40 PM](2734d27b-ef2b-469b-9344-a7c62cd502bc)\r\n\r\n###
BAD LLM\r\n![Screenshot 2024-04-30 at 1
05\r\n47 PM](fb3025e5-5f5e-49e6-8277-7dba52cc76cd)\r\n\r\n###
Invalid API key\r\n![Screenshot 2024-04-30 at 1
06\r\n26 PM](67c1bdf6-6785-48ed-870a-ae24e8ac7e9f)\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[ ] If a plugin configuration key changed, check if it needs to
be\r\nallowlisted in the cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[ ] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"0288bb466403452dad0fe0958da9a2249a473072"}},"sourceBranch":"main","suggestedTargetBranches":["8.14"],"targetPullRequestStates":[{"branch":"8.14","label":"v8.14.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.15.0","labelRegex":"^v8.15.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/182201","number":182201,"mergeCommit":{"message":"[Playground]
Propagate Error message into FE (#182201)\n\n## Summary\r\n\r\n- Fix
error not being propagated into FE\r\n- Added tests\r\n\r\n##
UI\r\n\r\n### Rate limit error message:\r\n![Screenshot 2024-04-30 at 1
05\r\n40 PM](2734d27b-ef2b-469b-9344-a7c62cd502bc)\r\n\r\n###
BAD LLM\r\n![Screenshot 2024-04-30 at 1
05\r\n47 PM](fb3025e5-5f5e-49e6-8277-7dba52cc76cd)\r\n\r\n###
Invalid API key\r\n![Screenshot 2024-04-30 at 1
06\r\n26 PM](67c1bdf6-6785-48ed-870a-ae24e8ac7e9f)\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[ ] If a plugin configuration key changed, check if it needs to
be\r\nallowlisted in the cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[ ] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"0288bb466403452dad0fe0958da9a2249a473072"}}]}]
BACKPORT-->
This commit is contained in:
Samiul Monir 2024-05-01 12:00:21 -04:00 committed by GitHub
parent eb58ab6f5f
commit a37a090da8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 184 additions and 4 deletions

View file

@ -0,0 +1,95 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
IRouter,
KibanaRequest,
RequestHandlerContext,
RouteValidatorConfig,
} from '@kbn/core/server';
import { httpServiceMock, httpServerMock } from '@kbn/core/server/mocks';
/**
* Test helper that mocks Kibana's router and DRYs out various helper (callRoute, schema validation)
*/
type MethodType = 'get' | 'post' | 'put' | 'patch' | 'delete';
type PayloadType = 'params' | 'query' | 'body';
interface IMockRouter {
method: MethodType;
path: string;
context?: jest.Mocked<RequestHandlerContext>;
}
interface IMockRouterRequest {
body?: object;
query?: object;
params?: object;
}
type MockRouterRequest = KibanaRequest | IMockRouterRequest;
export class MockRouter {
public router!: jest.Mocked<IRouter>;
public method: MethodType;
public path: string;
public context: jest.Mocked<RequestHandlerContext>;
public payload?: PayloadType;
public response = httpServerMock.createResponseFactory();
constructor({ method, path, context = {} as jest.Mocked<RequestHandlerContext> }: IMockRouter) {
this.createRouter();
this.method = method;
this.path = path;
this.context = context;
}
public createRouter = () => {
this.router = httpServiceMock.createRouter();
};
public callRoute = async (request: MockRouterRequest) => {
const route = this.findRouteRegistration();
const [, handler] = route;
await handler(this.context, httpServerMock.createKibanaRequest(request as any), this.response);
};
/**
* Schema validation helpers
*/
public validateRoute = (request: MockRouterRequest) => {
const route = this.findRouteRegistration();
const [config] = route;
const validate = config.validate as RouteValidatorConfig<{}, {}, {}>;
const payloads = Object.keys(request) as PayloadType[];
payloads.forEach((payload: PayloadType) => {
const payloadValidation = validate[payload] as { validate(request: KibanaRequest): void };
const payloadRequest = request[payload] as KibanaRequest;
payloadValidation.validate(payloadRequest);
});
};
public shouldValidate = (request: MockRouterRequest) => {
expect(() => this.validateRoute(request)).not.toThrow();
};
public shouldThrow = (request: MockRouterRequest) => {
expect(() => this.validateRoute(request)).toThrow();
};
private findRouteRegistration = () => {
const routerCalls = this.router[this.method].mock.calls as any[];
if (!routerCalls.length) throw new Error('No routes registered.');
const route = routerCalls.find(([router]: any) => router.path === this.path);
if (!route) throw new Error('No matching registered routes found - check method/path keys');
return route;
};
}

View file

@ -5,7 +5,19 @@
* 2.0.
*/
import { createRetriever } from './routes';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { RequestHandlerContext } from '@kbn/core/server';
import { coreMock } from '@kbn/core/server/mocks';
import { MockRouter } from '../__mocks__/router.mock';
import { ConversationalChain } from './lib/conversational_chain';
import { getChatParams } from './lib/get_chat_params';
import { createRetriever, defineRoutes } from './routes';
jest.mock('./lib/get_chat_params', () => ({
getChatParams: jest.fn(),
}));
jest.mock('./lib/conversational_chain');
describe('createRetriever', () => {
test('works when the question has quotes', () => {
@ -18,3 +30,75 @@ describe('createRetriever', () => {
expect(result).toEqual({ query: { match: { text: 'How can I "do something" with quotes?' } } });
});
});
describe('Search Playground routes', () => {
let mockRouter: MockRouter;
const mockClient = {
asCurrentUser: {},
};
const mockCore = {
elasticsearch: { client: mockClient },
};
const mockLogger = loggingSystemMock.createLogger().get();
describe('POST - Chat Messages', () => {
const mockData = {
connector_id: 'open-ai',
indices: 'my-index',
prompt: 'You are an assistant',
citations: true,
elasticsearch_query: {},
summarization_model: 'GPT-4',
doc_size: 3,
source_fields: '{}',
};
const mockRequestBody = {
data: mockData,
};
beforeEach(() => {
jest.clearAllMocks();
const coreStart = coreMock.createStart();
const context = {
core: Promise.resolve(mockCore),
} as unknown as jest.Mocked<RequestHandlerContext>;
mockRouter = new MockRouter({
context,
method: 'post',
path: '/internal/search_playground/chat',
});
defineRoutes({
logger: mockLogger,
router: mockRouter.router,
getStartServices: jest.fn().mockResolvedValue([coreStart, {}, {}]),
});
});
it('responds with error message if stream throws an error', async () => {
(getChatParams as jest.Mock).mockResolvedValue({ model: 'open-ai' });
(ConversationalChain as jest.Mock).mockImplementation(() => {
return {
stream: jest
.fn()
.mockRejectedValue(new Error('Unexpected API error - Some Open AI error message')),
};
});
await mockRouter.callRoute({
body: mockRequestBody,
});
expect(mockRouter.response.badRequest).toHaveBeenCalledWith({
body: {
message: 'Unexpected API error - Some Open AI error message',
},
});
});
});
});

View file

@ -131,10 +131,10 @@ export function defineRoutes({
} catch (e) {
logger.error('Failed to create the chat stream', e);
if (typeof e === 'string') {
if (typeof e === 'object') {
return response.badRequest({
body: {
message: e,
message: e.message,
},
});
}

View file

@ -35,7 +35,8 @@
"@kbn/triggers-actions-ui-plugin",
"@kbn/elastic-assistant-common",
"@kbn/logging",
"@kbn/doc-links"
"@kbn/doc-links",
"@kbn/core-logging-server-mocks"
],
"exclude": [
"target/**/*",