From 82c87a7b6eafdce310d82ebdc01581c6f4dd7ee1 Mon Sep 17 00:00:00 2001 From: Elastic Machine Date: Wed, 31 Jul 2024 02:55:44 +1000 Subject: [PATCH 01/11] [main] Sync bundled packages with Package Storage (#189514) Automated by https://buildkite.com/elastic/package-storage-infra-kibana-discover-release-branches/builds/1035 --- fleet_packages.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fleet_packages.json b/fleet_packages.json index 76dd27e8c27f0..d0ee7ad089924 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -30,11 +30,11 @@ }, { "name": "elastic_agent", - "version": "1.20.0" + "version": "2.0.1" }, { "name": "endpoint", - "version": "8.14.0" + "version": "8.15.0" }, { "name": "fleet_server", @@ -52,10 +52,10 @@ }, { "name": "synthetics", - "version": "1.2.1" + "version": "1.2.2" }, { "name": "security_detection_engine", - "version": "8.14.3" + "version": "8.15.1" } ] \ No newline at end of file From fa6d4aa97f9c2dba4591465fa06d61637470bf8a Mon Sep 17 00:00:00 2001 From: Agustina Nahir Ruidiaz <61565784+agusruidiazgd@users.noreply.github.com> Date: Tue, 30 Jul 2024 19:11:00 +0200 Subject: [PATCH 02/11] [Security Solution] Uncommon processes table pagination popover not rendering in the correct position (#189201) ## Summary Table Pagination popover is not rendering in the correct position under Uncommon Processes table. This is a custom pagination used under explore pages. When user clicked on "Rows per page: 10" pagination button, the popover with options was appearing in the middle of the table (horizontally). With this fix it appears where it should. Issue: https://github.com/elastic/kibana/issues/188933 Before: https://github.com/user-attachments/assets/4bd42a7e-fe67-4193-9484-5e1d8252a405 Now: https://github.com/user-attachments/assets/58ba703e-7358-45d7-84d6-4ebbc74bcc2c ### Checklist Delete any items that are not applicable to this PR. - [ ] [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 --------- Co-authored-by: Elastic Machine --- .../authentications_host_table.test.tsx.snap | 4 ++-- .../authentications_user_table.test.tsx.snap | 4 ++-- .../paginated_table/__snapshots__/index.test.tsx.snap | 8 ++++++-- .../public/explore/components/paginated_table/index.tsx | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap b/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap index 22a13d29278da..a14cc0ab81115 100644 --- a/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap @@ -352,10 +352,10 @@ exports[`Authentication Host Table Component rendering it renders the host authe
- - + + = ({ onChange={onChange} sorting={tableSorting} /> - - + + {itemsPerRow && itemsPerRow.length > 0 && totalCount >= itemsPerRow[0].numberOfRow && ( From d88a1b4ede4b6fd0786d1d82b45e15c0d2023509 Mon Sep 17 00:00:00 2001 From: Umberto Pepato Date: Tue, 30 Jul 2024 19:14:30 +0200 Subject: [PATCH 03/11] [ResponseOps][Rules] Version unmute alert route (#188830) ## Summary Versions the `POST /api/alerting/rule/{rule_id}/alert/{alert_id}/_unmute` enpoint. ## References Parent issue: https://github.com/elastic/kibana/issues/187572 Closes #187574 ### Checklist - [x] [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 --------- Co-authored-by: Antonio --- .../routes/rule/apis/unmute_alert/index.ts | 12 ++++ .../rule/apis/unmute_alert/schemas/latest.ts | 7 +++ .../rule/apis/unmute_alert/schemas/v1.ts | 21 +++++++ .../rule/apis/unmute_alert/types/latest.ts | 8 +++ .../routes/rule/apis/unmute_alert/types/v1.ts | 10 ++++ .../methods/unmute_alert/schemas/index.ts | 7 +++ .../schemas/unmute_alert_params_schema.ts | 12 ++++ .../rule/methods/unmute_alert/types/index.ts | 8 +++ .../unmute_alert/types/unmute_alert_params.ts | 11 ++++ .../unmute_alert}/unmute_instance.test.ts | 27 ++++++--- .../methods/unmute_alert}/unmute_instance.ts | 60 ++++++++++--------- .../plugins/alerting/server/routes/index.ts | 2 +- .../apis/unmute_alert/transforms/index.ts | 8 +++ .../latest.ts | 8 +++ .../v1.test.ts | 18 ++++++ .../v1.ts | 18 ++++++ .../unmute_alert/unmute_alert_route.test.ts} | 12 ++-- .../apis/unmute_alert/unmute_alert_route.ts} | 47 ++++++--------- .../server/rules_client/rules_client.ts | 5 +- 19 files changed, 227 insertions(+), 74 deletions(-) create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/index.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/schemas/latest.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/schemas/v1.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/types/latest.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/types/v1.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/schemas/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/schemas/unmute_alert_params_schema.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/types/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/types/unmute_alert_params.ts rename x-pack/plugins/alerting/server/{rules_client/tests => application/rule/methods/unmute_alert}/unmute_instance.test.ts (87%) rename x-pack/plugins/alerting/server/{rules_client/methods => application/rule/methods/unmute_alert}/unmute_instance.ts (56%) create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/latest.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/v1.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/v1.ts rename x-pack/plugins/alerting/server/routes/{unmute_alert.test.ts => rule/apis/unmute_alert/unmute_alert_route.test.ts} (84%) rename x-pack/plugins/alerting/server/routes/{unmute_alert.ts => rule/apis/unmute_alert/unmute_alert_route.ts} (58%) diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/index.ts new file mode 100644 index 0000000000000..202402bdda607 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/index.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +export { unmuteAlertParamsSchema } from './schemas/latest'; +export { unmuteAlertParamsSchema as unmuteAlertParamsSchemaV1 } from './schemas/v1'; + +export type { UnmuteAlertRequestParams } from './types/latest'; +export type { UnmuteAlertRequestParams as UnmuteAlertRequestParamsV1 } from './types/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/schemas/latest.ts new file mode 100644 index 0000000000000..e560bd87e0491 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/schemas/latest.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export { unmuteAlertParamsSchema } from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/schemas/v1.ts new file mode 100644 index 0000000000000..4ae0dccb96978 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/schemas/v1.ts @@ -0,0 +1,21 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const unmuteAlertParamsSchema = schema.object({ + rule_id: schema.string({ + meta: { + description: 'The identifier for the rule.', + }, + }), + alert_id: schema.string({ + meta: { + description: 'The identifier for the alert.', + }, + }), +}); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/types/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/types/latest.ts new file mode 100644 index 0000000000000..cab31be4e070e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/types/latest.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export type { UnmuteAlertRequestParams } from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/types/v1.ts new file mode 100644 index 0000000000000..0de4e0e767ceb --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/unmute_alert/types/v1.ts @@ -0,0 +1,10 @@ +/* + * 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 type { TypeOf } from '@kbn/config-schema'; +import { unmuteAlertParamsSchemaV1 } from '..'; + +export type UnmuteAlertRequestParams = TypeOf; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/schemas/index.ts new file mode 100644 index 0000000000000..7fc0a21218fcb --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/schemas/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export { unmuteAlertParamsSchema } from './unmute_alert_params_schema'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/schemas/unmute_alert_params_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/schemas/unmute_alert_params_schema.ts new file mode 100644 index 0000000000000..edc85497ded29 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/schemas/unmute_alert_params_schema.ts @@ -0,0 +1,12 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const unmuteAlertParamsSchema = schema.object({ + alertId: schema.string(), + alertInstanceId: schema.string(), +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/types/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/types/index.ts new file mode 100644 index 0000000000000..8d97bd968467c --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/types/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export type { UnmuteAlertParams } from './unmute_alert_params'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/types/unmute_alert_params.ts b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/types/unmute_alert_params.ts new file mode 100644 index 0000000000000..ae83c5b6d4b7e --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/types/unmute_alert_params.ts @@ -0,0 +1,11 @@ +/* + * 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 { TypeOf } from '@kbn/config-schema'; +import { unmuteAlertParamsSchema } from '../schemas'; + +export type UnmuteAlertParams = TypeOf; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/unmute_instance.test.ts similarity index 87% rename from x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts rename to x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/unmute_instance.test.ts index 948b9f8622002..f88b650c322ac 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/unmute_instance.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { RulesClient, ConstructorOptions } from '../rules_client'; +import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client'; import { savedObjectsClientMock, loggingSystemMock, @@ -13,17 +13,17 @@ import { uiSettingsServiceMock, } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; -import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; -import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; +import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock'; +import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; -import { AlertingAuthorization } from '../../authorization/alerting_authorization'; +import { AlertingAuthorization } from '../../../../authorization/alerting_authorization'; import { ActionsAuthorization } from '@kbn/actions-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; -import { getBeforeSetup, setGlobalDate } from './lib'; -import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; -import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { backfillClientMock } from '../../backfill_client/backfill_client.mock'; +import { getBeforeSetup, setGlobalDate } from '../../../../rules_client/tests/lib'; +import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; +import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; +import { backfillClientMock } from '../../../../backfill_client/backfill_client.mock'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -203,6 +203,17 @@ describe('unmuteInstance()', () => { ruleTypeId: 'myType', }); }); + + test('throws an error if API params do not match the schema', async () => { + const rulesClient = new RulesClient(rulesClientParams); + await expect( + // @ts-expect-error: Wrong params for testing purposes + rulesClient.unmuteInstance({ alertId: 1 }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to validate params: [alertId]: expected value of type [string] but got [number]"` + ); + expect(unsecuredSavedObjectsClient.update).not.toHaveBeenCalled(); + }); }); describe('auditLogger', () => { diff --git a/x-pack/plugins/alerting/server/rules_client/methods/unmute_instance.ts b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/unmute_instance.ts similarity index 56% rename from x-pack/plugins/alerting/server/rules_client/methods/unmute_instance.ts rename to x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/unmute_instance.ts index 0b8e422f1a946..220a1b14e728c 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/unmute_instance.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/unmute_alert/unmute_instance.ts @@ -5,39 +5,43 @@ * 2.0. */ -import { Rule, RawRule } from '../../types'; -import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; -import { retryIfConflicts } from '../../lib/retry_if_conflicts'; -import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; -import { MuteOptions } from '../types'; -import { RulesClientContext } from '../types'; -import { updateMeta } from '../lib'; -import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import Boom from '@hapi/boom'; +import type { Rule } from '../../../../types'; +import type { RulesClientContext } from '../../../../rules_client/types'; +import type { UnmuteAlertParams } from './types'; +import { retryIfConflicts } from '../../../../lib/retry_if_conflicts'; +import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; +import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events'; +import { unmuteAlertParamsSchema } from './schemas'; +import { updateMeta } from '../../../../rules_client/lib'; +import { updateRuleSo } from '../../../../data/rule'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization'; export async function unmuteInstance( context: RulesClientContext, - { alertId, alertInstanceId }: MuteOptions + params: UnmuteAlertParams ): Promise { + const ruleId = params.alertId; + try { + unmuteAlertParamsSchema.validate(params); + } catch (error) { + throw Boom.badRequest(`Failed to validate params: ${error.message}`); + } + return await retryIfConflicts( context.logger, - `rulesClient.unmuteInstance('${alertId}')`, - async () => await unmuteInstanceWithOCC(context, { alertId, alertInstanceId }) + `rulesClient.unmuteInstance('${ruleId}')`, + async () => await unmuteInstanceWithOCC(context, params) ); } async function unmuteInstanceWithOCC( context: RulesClientContext, - { - alertId, - alertInstanceId, - }: { - alertId: string; - alertInstanceId: string; - } + { alertId: ruleId, alertInstanceId }: UnmuteAlertParams ) { const { attributes, version } = await context.unsecuredSavedObjectsClient.get( RULE_SAVED_OBJECT_TYPE, - alertId + ruleId ); try { @@ -54,7 +58,7 @@ async function unmuteInstanceWithOCC( context.auditLogger?.log( ruleAuditEvent({ action: RuleAuditAction.UNMUTE_ALERT, - savedObject: { type: RULE_SAVED_OBJECT_TYPE, id: alertId }, + savedObject: { type: RULE_SAVED_OBJECT_TYPE, id: ruleId }, error, }) ); @@ -65,7 +69,7 @@ async function unmuteInstanceWithOCC( ruleAuditEvent({ action: RuleAuditAction.UNMUTE_ALERT, outcome: 'unknown', - savedObject: { type: RULE_SAVED_OBJECT_TYPE, id: alertId }, + savedObject: { type: RULE_SAVED_OBJECT_TYPE, id: ruleId }, }) ); @@ -73,15 +77,15 @@ async function unmuteInstanceWithOCC( const mutedInstanceIds = attributes.mutedInstanceIds || []; if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) { - await context.unsecuredSavedObjectsClient.update( - RULE_SAVED_OBJECT_TYPE, - alertId, - updateMeta(context, { + await updateRuleSo({ + savedObjectsClient: context.unsecuredSavedObjectsClient, + savedObjectsUpdateOptions: { version }, + id: ruleId, + updateRuleAttributes: updateMeta(context, { + mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), updatedBy: await context.getUserName(), updatedAt: new Date().toISOString(), - mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), }), - { version } - ); + }); } } diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index 648d661d1d612..c1fd477922fb9 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -34,7 +34,7 @@ import { ruleTypesRoute } from './rule_types'; import { muteAllRuleRoute } from './mute_all_rule'; import { muteAlertRoute } from './rule/apis/mute_alert/mute_alert'; import { unmuteAllRuleRoute } from './unmute_all_rule'; -import { unmuteAlertRoute } from './unmute_alert'; +import { unmuteAlertRoute } from './rule/apis/unmute_alert/unmute_alert_route'; import { updateRuleApiKeyRoute } from './rule/apis/update_api_key/update_rule_api_key_route'; import { bulkEditInternalRulesRoute } from './rule/apis/bulk_edit/bulk_edit_rules_route'; import { snoozeRuleRoute } from './rule/apis/snooze'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/index.ts new file mode 100644 index 0000000000000..21a7250aed4e2 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ +export { transformRequestParamsToApplication } from './transform_request_params_to_application/latest'; +export { transformRequestParamsToApplication as transformRequestParamsToApplicationV1 } from './transform_request_params_to_application/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/latest.ts new file mode 100644 index 0000000000000..5983069f0d8fd --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/latest.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { transformRequestParamsToApplication } from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/v1.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/v1.test.ts new file mode 100644 index 0000000000000..620d1ec4a746b --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/v1.test.ts @@ -0,0 +1,18 @@ +/* + * 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 { transformRequestParamsToApplication } from '..'; + +describe('transformRequestParamsToApplication', () => { + it('changes the parameters case', () => { + const transformed = transformRequestParamsToApplication({ + rule_id: 'test-rule-id', + alert_id: 'test-alert-id', + }); + expect(transformed).toEqual({ alertId: 'test-rule-id', alertInstanceId: 'test-alert-id' }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/v1.ts new file mode 100644 index 0000000000000..227b57ba67717 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/transforms/transform_request_params_to_application/v1.ts @@ -0,0 +1,18 @@ +/* + * 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 { UnmuteAlertParams } from '../../../../../../application/rule/methods/unmute_alert/types'; +import { RewriteRequestCase } from '../../../../../lib'; +import { UnmuteAlertRequestParamsV1 } from '../../../../../../../common/routes/rule/apis/unmute_alert'; + +export const transformRequestParamsToApplication: RewriteRequestCase = ({ + rule_id: alertId, + alert_id: alertInstanceId, +}: UnmuteAlertRequestParamsV1) => ({ + alertId, + alertInstanceId, +}); diff --git a/x-pack/plugins/alerting/server/routes/unmute_alert.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/unmute_alert_route.test.ts similarity index 84% rename from x-pack/plugins/alerting/server/routes/unmute_alert.test.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/unmute_alert_route.test.ts index 6f9c553831e77..cc58bd3d93d5c 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_alert.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/unmute_alert_route.test.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { unmuteAlertRoute } from './unmute_alert'; +import { unmuteAlertRoute } from './unmute_alert_route'; import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { rulesClientMock } from '../rules_client.mock'; -import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; +import { RuleTypeDisabledError } from '../../../../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); -jest.mock('../lib/license_api_access', () => ({ +jest.mock('../../../../lib/license_api_access', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/unmute_alert.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/unmute_alert_route.ts similarity index 58% rename from x-pack/plugins/alerting/server/routes/unmute_alert.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/unmute_alert_route.ts index ba3c639a88e52..fa608405343b0 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_alert.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unmute_alert/unmute_alert_route.ts @@ -6,32 +6,14 @@ */ import { IRouter } from '@kbn/core/server'; -import { schema } from '@kbn/config-schema'; -import { ILicenseState, RuleTypeDisabledError } from '../lib'; -import { MuteOptions } from '../rules_client'; -import { RewriteRequestCase, verifyAccessAndContext } from './lib'; -import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; - -const paramSchema = schema.object({ - rule_id: schema.string({ - meta: { - description: 'The identifier for the rule.', - }, - }), - alert_id: schema.string({ - meta: { - description: 'The identifier for the alert.', - }, - }), -}); - -const rewriteParamsReq: RewriteRequestCase = ({ - rule_id: alertId, - alert_id: alertInstanceId, -}) => ({ - alertId, - alertInstanceId, -}); +import { ILicenseState, RuleTypeDisabledError } from '../../../../lib'; +import { verifyAccessAndContext } from '../../../lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../../../../types'; +import { + unmuteAlertParamsSchemaV1, + UnmuteAlertRequestParamsV1, +} from '../../../../../common/routes/rule/apis/unmute_alert'; +import { transformRequestParamsToApplicationV1 } from './transforms'; export const unmuteAlertRoute = ( router: IRouter, @@ -45,15 +27,22 @@ export const unmuteAlertRoute = ( summary: `Unmute an alert`, }, validate: { - params: paramSchema, + request: { + params: unmuteAlertParamsSchemaV1, + }, + response: { + 204: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors( verifyAccessAndContext(licenseState, async function (context, req, res) { const rulesClient = (await context.alerting).getRulesClient(); - const params = rewriteParamsReq(req.params); + const params: UnmuteAlertRequestParamsV1 = req.params; try { - await rulesClient.unmuteInstance(params); + await rulesClient.unmuteInstance(transformRequestParamsToApplicationV1(params)); return res.noContent(); } catch (e) { if (e instanceof RuleTypeDisabledError) { diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d03bd3f59486b..343fbec059940 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { UnmuteAlertParams } from '../application/rule/methods/unmute_alert/types'; import { getRuleTags, RuleTagsParams } from '../application/rule/methods/tags'; import { MuteAlertParams } from '../application/rule/methods/mute_alert/types'; import { SanitizedRule, RuleTypeParams } from '../types'; @@ -60,7 +61,7 @@ import { clearExpiredSnoozes } from './methods/clear_expired_snoozes'; import { muteInstance } from '../application/rule/methods/mute_alert/mute_instance'; import { muteAll } from './methods/mute_all'; import { unmuteAll } from './methods/unmute_all'; -import { unmuteInstance } from './methods/unmute_instance'; +import { unmuteInstance } from '../application/rule/methods/unmute_alert/unmute_instance'; import { runSoon } from './methods/run_soon'; import { listRuleTypes } from './methods/list_rule_types'; import { getAlertFromRaw, GetAlertFromRawParams } from './lib/get_alert_from_raw'; @@ -181,7 +182,7 @@ export class RulesClient { public muteAll = (options: { id: string }) => muteAll(this.context, options); public unmuteAll = (options: { id: string }) => unmuteAll(this.context, options); public muteInstance = (options: MuteAlertParams) => muteInstance(this.context, options); - public unmuteInstance = (options: MuteAlertParams) => unmuteInstance(this.context, options); + public unmuteInstance = (options: UnmuteAlertParams) => unmuteInstance(this.context, options); public bulkUntrackAlerts = (options: BulkUntrackBody) => bulkUntrackAlerts(this.context, options); From fafdd16be73c84b44ed86de2996fea97b3ec0bf4 Mon Sep 17 00:00:00 2001 From: Kevin Lacabane Date: Tue, 30 Jul 2024 19:18:21 +0200 Subject: [PATCH 04/11] [eem] use api key clients to check enablement state (#189502) --- .../entity_manager/server/lib/utils.ts | 48 +++++++------------ .../server/routes/enablement/check.ts | 4 +- 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/utils.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/utils.ts index d948ac28bda31..d1d76e147efb0 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/utils.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/utils.ts @@ -5,37 +5,21 @@ * 2.0. */ -import moment from 'moment'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { getFakeKibanaRequest } from '@kbn/security-plugin/server/authentication/api_keys/fake_kibana_request'; +import { EntityManagerServerSetup } from '../types'; +import { EntityDiscoveryAPIKey } from './auth/api_key/api_key'; -export function toArray(maybeArray: T | T[] | undefined): T[] { - if (!maybeArray) { - return []; - } - if (Array.isArray(maybeArray)) { - return maybeArray; - } - return [maybeArray]; -} - -export const isValidRange = (from: string, to: string): boolean => { - if (moment(from).isAfter(to)) { - return false; - } - return true; +export const getClientsFromAPIKey = ({ + apiKey, + server, +}: { + apiKey: EntityDiscoveryAPIKey; + server: EntityManagerServerSetup; +}): { esClient: ElasticsearchClient; soClient: SavedObjectsClientContract } => { + const fakeRequest = getFakeKibanaRequest({ id: apiKey.id, api_key: apiKey.apiKey }); + const esClient = server.core.elasticsearch.client.asScoped(fakeRequest).asCurrentUser; + const soClient = server.core.savedObjects.getScopedClient(fakeRequest); + return { esClient, soClient }; }; - -export function isStringOrNonEmptyArray( - value: string | string[] | undefined -): value is string | string[] { - if (typeof value === 'undefined') { - return false; - } - if (Array.isArray(value) && value.length === 0) { - return false; - } - return true; -} - -export function extractFieldValue(maybeArray: T | T[] | undefined): T { - return toArray(maybeArray)[0]; -} diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/check.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/check.ts index 8ee8de3751ab2..23343026e3332 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/check.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/check.ts @@ -16,6 +16,7 @@ import { } from '../../../common/errors'; import { findEntityDefinitions } from '../../lib/entities/find_entity_definition'; import { builtInDefinitions } from '../../lib/entities/built_in'; +import { getClientsFromAPIKey } from '../../lib/utils'; export function checkEntityDiscoveryEnabledRoute({ router, @@ -43,8 +44,7 @@ export function checkEntityDiscoveryEnabledRoute { From 64c61e04422eba0e66e290c8a82367473de18f42 Mon Sep 17 00:00:00 2001 From: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:53:16 +0100 Subject: [PATCH 05/11] [Console Monaco] Improve color contrast of selection highlighting (#189060) Closes https://github.com/elastic/kibana/issues/186772 ## Summary This PR fixes the color contrast issue with the selection highlighting in Console Monaco. Before: Screenshot 2024-07-24 at 12 49 09 Now: Screenshot 2024-07-24 at 11 59 08 Note: I changed the request highlighting color from grey to blue (`euiColorPrimary`) since we are soon starting work on the Console redesign project, in which the new request highlighting color is blue (see screenshot from Figma project below). With the blue color, the contrast seems to be much higher.
Screenshot from Figma project Screenshot 2024-07-24 at 12 53 55
--------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/console/public/styles/_app.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/console/public/styles/_app.scss b/src/plugins/console/public/styles/_app.scss index f2353821ec93f..161fd913c32ae 100644 --- a/src/plugins/console/public/styles/_app.scss +++ b/src/plugins/console/public/styles/_app.scss @@ -131,7 +131,7 @@ * The highlighting for the selected requests in the monaco editor */ .console__monaco_editor__selectedRequests { - background: transparentize($euiColorLightShade, .3); + background: transparentize($euiColorPrimary, .9); } /* * The z-index for the autocomplete suggestions popup From dc11f75bd4ce64aff44871d1fbbaaba867753bdb Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 30 Jul 2024 13:02:03 -0500 Subject: [PATCH 06/11] [Security Assistant] Fix system prompts (#189130) --- .../conversation_settings_editor.tsx | 4 + .../index.tsx | 1 + .../impl/assistant/index.tsx | 15 ++- .../system_prompt/helpers.test.tsx | 10 +- .../prompt_editor/system_prompt/helpers.tsx | 31 ++++- .../prompt_editor/system_prompt/index.tsx | 24 +++- .../select_system_prompt/index.tsx | 24 +++- .../public/assistant/provider.test.tsx | 109 +++++++++++++++++- .../public/assistant/provider.tsx | 30 +++-- 9 files changed, 217 insertions(+), 31 deletions(-) diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx index 85b6360b409be..af6ac01fb18a4 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx @@ -13,6 +13,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; import { noop } from 'lodash/fp'; import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { QueryObserverResult } from '@tanstack/react-query'; import { Conversation } from '../../../..'; import * as i18n from './translations'; import * as i18nModel from '../../../connectorland/models/model_selector/translations'; @@ -37,6 +38,7 @@ export interface ConversationSettingsEditorProps { React.SetStateAction >; onSelectedConversationChange: (conversation?: Conversation) => void; + refetchConversations?: () => Promise, unknown>>; } /** @@ -53,6 +55,7 @@ export const ConversationSettingsEditor: React.FC { const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ http, @@ -276,6 +279,7 @@ export const ConversationSettingsEditor: React.FC = ({ conversationsSettingsBulkActions={conversationsSettingsBulkActions} http={http} isDisabled={isDisabled} + refetchConversations={refetchConversations} selectedConversation={selectedConversation} setConversationSettings={setConversationSettings} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx index e4457a8f38d4d..ff2ead2aeb386 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx @@ -256,6 +256,13 @@ const AssistantComponent: React.FC = ({ conversations[WELCOME_CONVERSATION_TITLE] ?? getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE }); + // updated selected system prompt + setEditingSystemPromptId( + getDefaultSystemPrompt({ + allSystemPrompts, + conversation: conversationToReturn, + })?.id + ); if ( prev && prev.id === conversationToReturn.id && @@ -273,6 +280,7 @@ const AssistantComponent: React.FC = ({ }); } }, [ + allSystemPrompts, areConnectorsFetched, conversationTitle, conversations, @@ -647,6 +655,7 @@ const AssistantComponent: React.FC = ({ actionTypeId: (defaultConnector?.actionTypeId as string) ?? '.gen-ai', provider: apiConfig?.apiProvider, model: apiConfig?.defaultModel, + defaultSystemPromptId: allSystemPrompts.find((sp) => sp.isNewConversationDefault)?.id, }, }); }, @@ -665,14 +674,14 @@ const AssistantComponent: React.FC = ({ useEffect(() => { (async () => { - if (areConnectorsFetched && currentConversation?.id === '') { + if (areConnectorsFetched && currentConversation?.id === '' && !isLoadingPrompts) { const conversation = await mutateAsync(currentConversation); if (currentConversation.id === '' && conversation) { setCurrentConversationId(conversation.id); } } })(); - }, [areConnectorsFetched, currentConversation, mutateAsync]); + }, [areConnectorsFetched, currentConversation, isLoadingPrompts, mutateAsync]); const handleCreateConversation = useCallback(async () => { const newChatExists = find(conversations, ['title', NEW_CHAT]); @@ -791,6 +800,7 @@ const AssistantComponent: React.FC = ({ isSettingsModalVisible={isSettingsModalVisible} setIsSettingsModalVisible={setIsSettingsModalVisible} allSystemPrompts={allSystemPrompts} + refetchConversations={refetchResults} />
@@ -823,6 +833,7 @@ const AssistantComponent: React.FC = ({ handleOnSystemPromptSelectionChange, isSettingsModalVisible, isWelcomeSetup, + refetchResults, ]); return ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.test.tsx index f13441a3102f9..9feb3e53aa8bc 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.test.tsx @@ -16,13 +16,13 @@ import { getOptions, getOptionFromPrompt } from './helpers'; describe('helpers', () => { describe('getOptionFromPrompt', () => { it('returns an EuiSuperSelectOption with the correct value', () => { - const option = getOptionFromPrompt({ ...mockSystemPrompt }); + const option = getOptionFromPrompt({ ...mockSystemPrompt, isCleared: false }); expect(option.value).toBe(mockSystemPrompt.id); }); it('returns an EuiSuperSelectOption with the correct inputDisplay', () => { - const option = getOptionFromPrompt({ ...mockSystemPrompt }); + const option = getOptionFromPrompt({ ...mockSystemPrompt, isCleared: false }); render(<>{option.inputDisplay}); @@ -30,7 +30,7 @@ describe('helpers', () => { }); it('shows the expected name in the dropdownDisplay', () => { - const option = getOptionFromPrompt({ ...mockSystemPrompt }); + const option = getOptionFromPrompt({ ...mockSystemPrompt, isCleared: false }); render({option.dropdownDisplay}); @@ -38,7 +38,7 @@ describe('helpers', () => { }); it('shows the expected prompt content in the dropdownDisplay', () => { - const option = getOptionFromPrompt({ ...mockSystemPrompt }); + const option = getOptionFromPrompt({ ...mockSystemPrompt, isCleared: false }); render({option.dropdownDisplay}); @@ -51,7 +51,7 @@ describe('helpers', () => { const prompts = [mockSystemPrompt, mockSuperheroSystemPrompt]; const promptIds = prompts.map(({ id }) => id); - const options = getOptions({ prompts }); + const options = getOptions({ prompts, isCleared: false }); const optionValues = options.map(({ value }) => value); expect(optionValues).toEqual(promptIds); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx index 92814927f980a..b5efd08b28f9c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx @@ -12,19 +12,38 @@ import styled from '@emotion/styled'; import { isEmpty } from 'lodash/fp'; import { euiThemeVars } from '@kbn/ui-theme'; import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { css } from '@emotion/react'; import { EMPTY_PROMPT } from './translations'; const Strong = styled.strong` margin-right: ${euiThemeVars.euiSizeS}; `; +interface GetOptionFromPromptProps extends PromptResponse { + content: string; + id: string; + name: string; + isCleared: boolean; +} + export const getOptionFromPrompt = ({ content, id, + isCleared, name, -}: PromptResponse): EuiSuperSelectOption => ({ +}: GetOptionFromPromptProps): EuiSuperSelectOption => ({ value: id, - inputDisplay: {name}, + inputDisplay: ( + + {name} + + ), dropdownDisplay: ( <> {name} @@ -41,6 +60,10 @@ export const getOptionFromPrompt = ({ interface GetOptionsProps { prompts: PromptResponse[] | undefined; + isCleared: boolean; } -export const getOptions = ({ prompts }: GetOptionsProps): Array> => - prompts?.map(getOptionFromPrompt) ?? []; +export const getOptions = ({ + prompts, + isCleared, +}: GetOptionsProps): Array> => + prompts?.map((p) => getOptionFromPrompt({ ...p, isCleared })) ?? []; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx index 01fe334eb1f7d..592d91fe98326 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { QueryObserverResult } from '@tanstack/react-query'; import { Conversation } from '../../../..'; import { SelectSystemPrompt } from './select_system_prompt'; @@ -17,6 +18,7 @@ interface Props { onSystemPromptSelectionChange: (systemPromptId: string | undefined) => void; setIsSettingsModalVisible: React.Dispatch>; allSystemPrompts: PromptResponse[]; + refetchConversations?: () => Promise, unknown>>; } const SystemPromptComponent: React.FC = ({ @@ -26,9 +28,12 @@ const SystemPromptComponent: React.FC = ({ onSystemPromptSelectionChange, setIsSettingsModalVisible, allSystemPrompts, + refetchConversations, }) => { + const [isCleared, setIsCleared] = useState(false); const selectedPrompt = useMemo(() => { if (editingSystemPromptId !== undefined) { + setIsCleared(false); return allSystemPrompts.find((p) => p.id === editingSystemPromptId); } else { return allSystemPrompts.find((p) => p.id === conversation?.apiConfig?.defaultSystemPromptId); @@ -36,10 +41,21 @@ const SystemPromptComponent: React.FC = ({ }, [allSystemPrompts, conversation?.apiConfig?.defaultSystemPromptId, editingSystemPromptId]); const handleClearSystemPrompt = useCallback(() => { - if (conversation) { + if (editingSystemPromptId === undefined) { + setIsCleared(false); + onSystemPromptSelectionChange( + allSystemPrompts.find((p) => p.id === conversation?.apiConfig?.defaultSystemPromptId)?.id + ); + } else { + setIsCleared(true); onSystemPromptSelectionChange(undefined); } - }, [conversation, onSystemPromptSelectionChange]); + }, [ + allSystemPrompts, + conversation?.apiConfig?.defaultSystemPromptId, + editingSystemPromptId, + onSystemPromptSelectionChange, + ]); return ( = ({ conversation={conversation} data-test-subj="systemPrompt" isClearable={true} + isCleared={isCleared} + refetchConversations={refetchConversations} isSettingsModalVisible={isSettingsModalVisible} onSystemPromptSelectionChange={onSystemPromptSelectionChange} selectedPrompt={selectedPrompt} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx index fbe0f40320c4c..08a6888b30626 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx @@ -22,6 +22,7 @@ import { PromptResponse, PromptTypeEnum, } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; +import { QueryObserverResult } from '@tanstack/react-query'; import { Conversation } from '../../../../..'; import { getOptions } from '../helpers'; import * as i18n from '../translations'; @@ -38,6 +39,7 @@ export interface Props { selectedPrompt: PromptResponse | undefined; clearSelectedSystemPrompt?: () => void; isClearable?: boolean; + isCleared?: boolean; isDisabled?: boolean; isOpen?: boolean; isSettingsModalVisible: boolean; @@ -46,6 +48,7 @@ export interface Props { onSelectedConversationChange?: (result: Conversation) => void; setConversationSettings?: React.Dispatch>>; setConversationsSettingsBulkActions?: React.Dispatch>; + refetchConversations?: () => Promise, unknown>>; } const ADD_NEW_SYSTEM_PROMPT = 'ADD_NEW_SYSTEM_PROMPT'; @@ -57,8 +60,10 @@ const SelectSystemPromptComponent: React.FC = ({ selectedPrompt, clearSelectedSystemPrompt, isClearable = false, + isCleared = false, isDisabled = false, isOpen = false, + refetchConversations, isSettingsModalVisible, onSystemPromptSelectionChange, setIsSettingsModalVisible, @@ -89,10 +94,11 @@ const SelectSystemPromptComponent: React.FC = ({ defaultSystemPromptId: promptId, }, }); + await refetchConversations?.(); return result; } }, - [conversation, setApiConfig] + [conversation, refetchConversations, setApiConfig] ); const addNewSystemPrompt = useMemo(() => { @@ -116,7 +122,10 @@ const SelectSystemPromptComponent: React.FC = ({ }, []); // SuperSelect State/Actions - const options = useMemo(() => getOptions({ prompts: allSystemPrompts }), [allSystemPrompts]); + const options = useMemo( + () => getOptions({ prompts: allSystemPrompts, isCleared }), + [allSystemPrompts, isCleared] + ); const onChange = useCallback( async (selectedSystemPromptId) => { @@ -160,9 +169,8 @@ const SelectSystemPromptComponent: React.FC = ({ ); const clearSystemPrompt = useCallback(() => { - setSelectedSystemPrompt(undefined); clearSelectedSystemPrompt?.(); - }, [clearSelectedSystemPrompt, setSelectedSystemPrompt]); + }, [clearSelectedSystemPrompt]); return ( = ({ inline-size: 16px; block-size: 16px; border-radius: 16px; - background: ${euiThemeVars.euiColorMediumShade}; + background: ${isCleared + ? euiThemeVars.euiColorLightShade + : euiThemeVars.euiColorMediumShade}; :hover:not(:disabled) { - background: ${euiThemeVars.euiColorMediumShade}; + background: ${isCleared + ? euiThemeVars.euiColorLightShade + : euiThemeVars.euiColorMediumShade}; transform: none; } diff --git a/x-pack/plugins/security_solution/public/assistant/provider.test.tsx b/x-pack/plugins/security_solution/public/assistant/provider.test.tsx index 3667e077a50c1..0534df76aaf6e 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.test.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.test.tsx @@ -5,13 +5,38 @@ * 2.0. */ +import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; import { httpServiceMock, type HttpSetupMock } from '@kbn/core-http-browser-mocks'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; -import { createConversations } from './provider'; +import { AssistantProvider, createConversations } from './provider'; import { coreMock } from '@kbn/core/public/mocks'; +import { useKibana as mockUseKibana } from '../common/lib/kibana/__mocks__'; import { loadAllActions as loadConnectors } from '@kbn/triggers-actions-ui-plugin/public/common/constants'; +import { useKibana } from '../common/lib/kibana'; +import { render, waitFor } from '@testing-library/react'; +import { TestProviders } from '../common/mock'; +import { useAssistantAvailability } from './use_assistant_availability'; +import { + bulkUpdatePrompts, + getPrompts, + getUserConversations, +} from '@kbn/elastic-assistant/impl/assistant/api'; +import { BASE_SECURITY_SYSTEM_PROMPTS } from './content/prompts/system'; +const mockedUseKibana = mockUseKibana(); +jest.mock('./use_assistant_availability'); +jest.mock('../common/lib/kibana'); + +jest.mock('@kbn/elastic-assistant/impl/assistant/api'); +jest.mock('../common/hooks/use_license', () => ({ + useLicense: () => ({ + isEnterprise: () => true, + }), + licenseService: { + isEnterprise: () => true, + }, +})); jest.mock('@kbn/triggers-actions-ui-plugin/public/common/constants'); let http: HttpSetupMock = coreMock.createSetup().http; export const mockConnectors = [ @@ -199,3 +224,85 @@ describe('createConversations', () => { }); }); }); +describe('AssistantProvider', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useKibana as jest.Mock).mockReturnValue({ + ...mockedUseKibana, + services: { + ...mockedUseKibana.services, + }, + }); + jest.mocked(useAssistantAvailability).mockReturnValue({ + hasAssistantPrivilege: true, + hasConnectorsAllPrivilege: true, + hasConnectorsReadPrivilege: true, + hasUpdateAIAssistantAnonymization: true, + isAssistantEnabled: true, + }); + + (getUserConversations as jest.Mock).mockResolvedValue({ + page: 1, + perPage: 5, + total: 5, + data: [], + }); + (getPrompts as jest.Mock).mockResolvedValue({ + page: 1, + perPage: 5, + total: 0, + data: [], + }); + }); + it('should not render the assistant when no prompts have been returned', async () => { + const { queryByTestId } = render( + + + , + { + wrapper: TestProviders, + } + ); + expect(queryByTestId('ourAssistant')).toBeNull(); + }); + it('should render the assistant when prompts are returned', async () => { + (getPrompts as jest.Mock).mockResolvedValue({ + page: 1, + perPage: 5, + total: 2, + data: BASE_SECURITY_SYSTEM_PROMPTS, + }); + const { getByTestId } = render( + + + , + { + wrapper: TestProviders, + } + ); + await waitFor(() => { + expect(getByTestId('ourAssistant')).not.toBeNull(); + }); + }); + it('should render the assistant once prompts have been created', async () => { + (bulkUpdatePrompts as jest.Mock).mockResolvedValue({ + success: true, + attributes: { + results: { + created: BASE_SECURITY_SYSTEM_PROMPTS, + }, + }, + }); + const { getByTestId } = render( + + + , + { + wrapper: TestProviders, + } + ); + await waitFor(() => { + expect(getByTestId('ourAssistant')).not.toBeNull(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx index 134bfb25c15ac..dbfbb026ab2d3 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -5,7 +5,7 @@ * 2.0. */ import type { FC, PropsWithChildren } from 'react'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { parse } from '@kbn/datemath'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; import { i18n } from '@kbn/i18n'; @@ -128,7 +128,7 @@ export const createBasePrompts = async (notifications: NotificationsStart, http: notifications.toasts ); if (bulkResult && bulkResult.success) { - return true; + return bulkResult.attributes.results.created; } }; @@ -176,6 +176,8 @@ export const AssistantProvider: FC> = ({ children }) storage, ]); + const [basePromptsLoaded, setBasePromptsLoaded] = useState(false); + useEffect(() => { const createSecurityPrompts = once(async () => { if ( @@ -183,15 +185,20 @@ export const AssistantProvider: FC> = ({ children }) assistantAvailability.isAssistantEnabled && assistantAvailability.hasAssistantPrivilege ) { - const res = await getPrompts({ - http, - toasts: notifications.toasts, - }); + try { + const res = await getPrompts({ + http, + toasts: notifications.toasts, + }); - if (res.total === 0) { - await createBasePrompts(notifications, http); - } + if (res.total === 0) { + await createBasePrompts(notifications, http); + } + // eslint-disable-next-line no-empty + } catch (e) {} } + + setBasePromptsLoaded(true); }); createSecurityPrompts(); }, [ @@ -205,6 +212,9 @@ export const AssistantProvider: FC> = ({ children }) const { signalIndexName } = useSignalIndex(); const alertsIndexPattern = signalIndexName ?? undefined; const toasts = useAppToasts() as unknown as IToasts; // useAppToasts is the current, non-deprecated method of getting the toasts service in the Security Solution, but it doesn't return the IToasts interface (defined by core) + // Because our conversations need an assigned system prompt at create time, + // we want to make sure the prompts are there before creating the first conversation + // however if there is an error fetching the prompts, we don't want to block the app return ( > = ({ children }) toasts={toasts} currentAppId={currentAppId ?? 'securitySolutionUI'} > - {children} + {basePromptsLoaded ? children : null} ); }; From 6e622e9c2b8342f7451b8607065e16da126c5e93 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 30 Jul 2024 12:45:03 -0600 Subject: [PATCH 07/11] remove @kbn/presentation-containers from synthetics page load bundle (#189533) https://github.com/elastic/kibana/pull/189128 increases @kbn/presentation-containers size. This causes synthetics page load bundle to exceed the bundle size limit. Synthetics page load should not include @kbn/presentation-containers. This PR resolves the problem by putting @kbn/presentation-containers load behind an async import Note for reviewers, put compatibility check into its own file to avoid importing entire @kbn/presentation-containers into async bundle. Tree shaking is enable by putting the import in a seperate file - see https://github.com/elastic/kibana/pull/189206#discussion_r1695763844 for more details --- .../embeddables/ui_actions/compatibility_check.ts | 15 +++++++++++++++ .../ui_actions/create_overview_panel_action.tsx | 7 ++++--- 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/compatibility_check.ts diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/compatibility_check.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/compatibility_check.ts new file mode 100644 index 0000000000000..ff282745c8fc2 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/compatibility_check.ts @@ -0,0 +1,15 @@ +/* + * 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 { apiIsPresentationContainer, PresentationContainer } from '@kbn/presentation-containers'; +import { EmbeddableApiContext } from '@kbn/presentation-publishing'; + +export const compatibilityCheck = ( + api: EmbeddableApiContext['embeddable'] +): api is PresentationContainer => { + return apiIsPresentationContainer(api); +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/create_overview_panel_action.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/create_overview_panel_action.tsx index 202a09e1f3576..79c6a6c1195a9 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/create_overview_panel_action.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/create_overview_panel_action.tsx @@ -5,7 +5,6 @@ * 2.0. */ import { i18n } from '@kbn/i18n'; -import { apiIsPresentationContainer } from '@kbn/presentation-containers'; import { IncompatibleActionError, type UiActionsActionDefinition, @@ -34,10 +33,12 @@ export function createStatusOverviewPanelAction(): UiActionsActionDefinition 'online', isCompatible: async ({ embeddable }) => { - return apiIsPresentationContainer(embeddable); + const { compatibilityCheck } = await import('./compatibility_check'); + return compatibilityCheck(embeddable); }, execute: async ({ embeddable }) => { - if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError(); + const { compatibilityCheck } = await import('./compatibility_check'); + if (!compatibilityCheck(embeddable)) throw new IncompatibleActionError(); try { embeddable.addNewPanel({ panelType: SYNTHETICS_OVERVIEW_EMBEDDABLE, From ea620c4163d11518215e5ab7314a2382ff8ad883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 30 Jul 2024 20:47:04 +0200 Subject: [PATCH 08/11] [Move `@kbn/config-schema` to server] `@kbn/cli-dev-mode` (#189523) --- packages/kbn-cli-dev-mode/kibana.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-cli-dev-mode/kibana.jsonc b/packages/kbn-cli-dev-mode/kibana.jsonc index 3c55d047b0efc..fcacb342273f5 100644 --- a/packages/kbn-cli-dev-mode/kibana.jsonc +++ b/packages/kbn-cli-dev-mode/kibana.jsonc @@ -1,5 +1,5 @@ { - "type": "shared-common", + "type": "shared-server", "id": "@kbn/cli-dev-mode", "devOnly": true, "owner": "@elastic/kibana-operations" From d433b91918bb873da80c5f726aa1e0a538afb672 Mon Sep 17 00:00:00 2001 From: Joe McElroy Date: Tue, 30 Jul 2024 19:48:19 +0100 Subject: [PATCH 09/11] [Search] [Playground ]handle error LLM edge cases (#189509) ## Summary Fixes: - sending the error message as part of the conversation. The LLM picks up on it and reduces the effectiveness of the answer - clear chat is now enabled when error has occured - better error messages from retriever and LLM ### 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 - [x] [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) --- .../public/components/chat.tsx | 6 +-- .../server/lib/conversational_chain.test.ts | 39 +++++++++++++++++++ .../server/lib/conversational_chain.ts | 35 +++++++++-------- .../search_playground/server/routes.ts | 2 +- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/search_playground/public/components/chat.tsx b/x-pack/plugins/search_playground/public/components/chat.tsx index cc4c0b1ccdff2..b27955c326d23 100644 --- a/x-pack/plugins/search_playground/public/components/chat.tsx +++ b/x-pack/plugins/search_playground/public/components/chat.tsx @@ -56,7 +56,7 @@ export const Chat = () => { handleSubmit, getValues, } = useFormContext(); - const { messages, append, stop: stopRequest, setMessages, reload, error } = useChat(); + const { messages, append, stop: stopRequest, setMessages, reload } = useChat(); const messagesRef = useAutoBottomScroll(); const [isRegenerating, setIsRegenerating] = useState(false); const usageTracker = useUsageTracker(); @@ -88,8 +88,8 @@ export const Chat = () => { ); const isToolBarActionsDisabled = useMemo( - () => chatMessages.length <= 1 || !!error || isRegenerating || isSubmitting, - [chatMessages, error, isSubmitting, isRegenerating] + () => chatMessages.length <= 1 || isRegenerating || isSubmitting, + [chatMessages, isSubmitting, isRegenerating] ); const regenerateMessages = async () => { diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts index 88a6052a0bbf7..13959e4455c29 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts @@ -305,6 +305,45 @@ describe('conversational chain', () => { }); }, 10000); + it('should omit the system messages in chat', async () => { + await createTestChain({ + responses: ['the final answer'], + chat: [ + { + id: '1', + role: MessageRole.user, + content: 'what is the work from home policy?', + }, + { + id: '2', + role: MessageRole.system, + content: 'Error occurred. Please try again.', + }, + ], + expectedFinalAnswer: 'the final answer', + expectedDocs: [ + { + documents: [ + { metadata: { _id: '1', _index: 'index' }, pageContent: 'value' }, + { metadata: { _id: '1', _index: 'website' }, pageContent: 'value2' }, + ], + type: 'retrieved_docs', + }, + ], + expectedTokens: [ + { type: 'context_token_count', count: 15 }, + { type: 'prompt_token_count', count: 28 }, + ], + expectedSearchRequest: [ + { + method: 'POST', + path: '/index,website/_search', + body: { query: { match: { field: 'what is the work from home policy?' } }, size: 3 }, + }, + ], + }); + }, 10000); + it('should cope with quotes in the query', async () => { await createTestChain({ responses: ['rewrite "the" question', 'the final answer'], diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts index f7b1634dd27b1..c63481e93c98f 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts @@ -18,7 +18,7 @@ import { createStreamDataTransformer, experimental_StreamData } from 'ai'; import { BaseLanguageModel } from '@langchain/core/language_models/base'; import { BaseMessage } from '@langchain/core/messages'; import { HumanMessage, AIMessage } from '@langchain/core/messages'; -import { ChatMessage, MessageRole } from '../types'; +import { ChatMessage } from '../types'; import { ElasticsearchRetriever } from './elasticsearch_retriever'; import { renderTemplate } from '../utils/render_template'; @@ -49,25 +49,28 @@ interface ContextInputs { question: string; } -const getSerialisedMessages = (chatHistory: ChatMessage[]) => { +const getSerialisedMessages = (chatHistory: BaseMessage[]) => { const formattedDialogueTurns = chatHistory.map((message) => { - if (message.role === MessageRole.user) { + if (message instanceof HumanMessage) { return `Human: ${message.content}`; - } else if (message.role === MessageRole.assistant) { + } else if (message instanceof AIMessage) { return `Assistant: ${message.content}`; } }); return formattedDialogueTurns.join('\n'); }; -const getMessages = (chatHistory: ChatMessage[]) => { - return chatHistory.map((message) => { - if (message.role === 'human') { - return new HumanMessage(message.content); - } else { - return new AIMessage(message.content); - } - }); +export const getMessages = (chatHistory: ChatMessage[]) => { + return chatHistory + .map((message) => { + if (message.role === 'human') { + return new HumanMessage(message.content); + } else if (message.role === 'assistant') { + return new AIMessage(message.content); + } + return null; + }) + .filter((message): message is BaseMessage => message !== null); }; const buildContext = (docs: Document[]) => { @@ -141,8 +144,9 @@ class ConversationalChainFn { const data = new experimental_StreamData(); const messages = msgs ?? []; - const previousMessages = messages.slice(0, -1); - const question = messages[messages.length - 1]!.content; + const lcMessages = getMessages(messages); + const previousMessages = lcMessages.slice(0, -1); + const question = lcMessages[lcMessages.length - 1]!.content; const retrievedDocs: Document[] = []; let retrievalChain: Runnable = RunnableLambda.from(() => ''); @@ -165,7 +169,7 @@ class ConversationalChainFn { return input.question; }); - if (previousMessages.length > 0) { + if (lcMessages.length > 1) { const questionRewritePromptTemplate = PromptTemplate.fromTemplate( this.options.questionRewritePrompt ); @@ -184,7 +188,6 @@ class ConversationalChainFn { }); } - const lcMessages = getMessages(messages); const prompt = ChatPromptTemplate.fromMessages([ SystemMessagePromptTemplate.fromTemplate(this.options.prompt), ...lcMessages, diff --git a/x-pack/plugins/search_playground/server/routes.ts b/x-pack/plugins/search_playground/server/routes.ts index de26776816f33..a71e650a8dae8 100644 --- a/x-pack/plugins/search_playground/server/routes.ts +++ b/x-pack/plugins/search_playground/server/routes.ts @@ -31,7 +31,7 @@ export function createRetriever(esQuery: string) { const query = JSON.parse(replacedQuery); return query; } catch (e) { - throw Error(e); + throw Error("Failed to parse the Elasticsearch Query. Check Query to make sure it's valid."); } }; } From f6b37d9e53f3854543d4b2eb916be7cb2d79918c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 30 Jul 2024 20:57:37 +0200 Subject: [PATCH 10/11] [Move `@kbn/config-schema` to server] `examples/response_stream` (#189520) --- .../response_stream/common/api/reducer_stream/index.ts | 2 -- .../response_stream/server/routes/reducer_stream.ts | 2 +- examples/response_stream/server/routes/redux_stream.ts | 2 +- .../server/routes/schemas/reducer_stream/index.ts | 10 ++++++++++ .../schemas}/reducer_stream/request_body_schema.ts | 0 .../routes/schemas}/simple_string_stream/index.ts | 0 .../simple_string_stream/request_body_schema.ts | 0 .../server/routes/single_string_stream.ts | 2 +- 8 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 examples/response_stream/server/routes/schemas/reducer_stream/index.ts rename examples/response_stream/{common/api => server/routes/schemas}/reducer_stream/request_body_schema.ts (100%) rename examples/response_stream/{common/api => server/routes/schemas}/simple_string_stream/index.ts (100%) rename examples/response_stream/{common/api => server/routes/schemas}/simple_string_stream/request_body_schema.ts (100%) diff --git a/examples/response_stream/common/api/reducer_stream/index.ts b/examples/response_stream/common/api/reducer_stream/index.ts index cc5255761fbd6..13f3c0274f771 100644 --- a/examples/response_stream/common/api/reducer_stream/index.ts +++ b/examples/response_stream/common/api/reducer_stream/index.ts @@ -7,5 +7,3 @@ */ export { reducerStreamReducer } from './reducer'; -export { reducerStreamRequestBodySchema } from './request_body_schema'; -export type { ReducerStreamRequestBodySchema } from './request_body_schema'; diff --git a/examples/response_stream/server/routes/reducer_stream.ts b/examples/response_stream/server/routes/reducer_stream.ts index abdc90f28a23c..9a84d3009b6c3 100644 --- a/examples/response_stream/server/routes/reducer_stream.ts +++ b/examples/response_stream/server/routes/reducer_stream.ts @@ -16,7 +16,7 @@ import { deleteEntityAction, ReducerStreamApiAction, } from '../../common/api/reducer_stream/reducer_actions'; -import { reducerStreamRequestBodySchema } from '../../common/api/reducer_stream'; +import { reducerStreamRequestBodySchema } from './schemas/reducer_stream'; import { RESPONSE_STREAM_API_ENDPOINT } from '../../common/api'; import { entities, getActions } from './shared'; diff --git a/examples/response_stream/server/routes/redux_stream.ts b/examples/response_stream/server/routes/redux_stream.ts index bd694c531907b..700e1ff3d06c4 100644 --- a/examples/response_stream/server/routes/redux_stream.ts +++ b/examples/response_stream/server/routes/redux_stream.ts @@ -16,7 +16,7 @@ import { error, type ReduxStreamApiAction, } from '../../common/api/redux_stream/data_slice'; -import { reducerStreamRequestBodySchema } from '../../common/api/reducer_stream'; +import { reducerStreamRequestBodySchema } from './schemas/reducer_stream'; import { RESPONSE_STREAM_API_ENDPOINT } from '../../common/api'; import { entities, getActions } from './shared'; diff --git a/examples/response_stream/server/routes/schemas/reducer_stream/index.ts b/examples/response_stream/server/routes/schemas/reducer_stream/index.ts new file mode 100644 index 0000000000000..62247bb3f3045 --- /dev/null +++ b/examples/response_stream/server/routes/schemas/reducer_stream/index.ts @@ -0,0 +1,10 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { reducerStreamRequestBodySchema } from './request_body_schema'; +export type { ReducerStreamRequestBodySchema } from './request_body_schema'; diff --git a/examples/response_stream/common/api/reducer_stream/request_body_schema.ts b/examples/response_stream/server/routes/schemas/reducer_stream/request_body_schema.ts similarity index 100% rename from examples/response_stream/common/api/reducer_stream/request_body_schema.ts rename to examples/response_stream/server/routes/schemas/reducer_stream/request_body_schema.ts diff --git a/examples/response_stream/common/api/simple_string_stream/index.ts b/examples/response_stream/server/routes/schemas/simple_string_stream/index.ts similarity index 100% rename from examples/response_stream/common/api/simple_string_stream/index.ts rename to examples/response_stream/server/routes/schemas/simple_string_stream/index.ts diff --git a/examples/response_stream/common/api/simple_string_stream/request_body_schema.ts b/examples/response_stream/server/routes/schemas/simple_string_stream/request_body_schema.ts similarity index 100% rename from examples/response_stream/common/api/simple_string_stream/request_body_schema.ts rename to examples/response_stream/server/routes/schemas/simple_string_stream/request_body_schema.ts diff --git a/examples/response_stream/server/routes/single_string_stream.ts b/examples/response_stream/server/routes/single_string_stream.ts index d9cb65686b71e..daf0ae682a14e 100644 --- a/examples/response_stream/server/routes/single_string_stream.ts +++ b/examples/response_stream/server/routes/single_string_stream.ts @@ -9,7 +9,7 @@ import type { IRouter, Logger } from '@kbn/core/server'; import { streamFactory } from '@kbn/ml-response-stream/server'; -import { simpleStringStreamRequestBodySchema } from '../../common/api/simple_string_stream'; +import { simpleStringStreamRequestBodySchema } from './schemas/simple_string_stream'; import { RESPONSE_STREAM_API_ENDPOINT } from '../../common/api'; function timeout(ms: number) { From 6c581d541b0c10c25d667f028866dbe6f51088c5 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Tue, 30 Jul 2024 13:21:09 -0600 Subject: [PATCH 11/11] [Embeddable Rebuild] [Controls] Fix apply button style bugs (#189433) Closes https://github.com/elastic/kibana/issues/189303 ## Summary > [!NOTE] > This PR has **no** user-facing changes - all work is contained in the `examples` plugin. This fixes the placement of both the time slider play button and the control group apply button: | Before | After | |--------|--------| | ![image](https://github.com/user-attachments/assets/a7854539-5758-45e0-b25e-676d09dddac1) | ![image](https://github.com/user-attachments/assets/c2d14b0e-a974-41cc-9db9-a097cb3ca803) | It also removes the tooltip when the apply button is enabled - this is due to a known EUI bug where the tooltip gets stuck when a button switches from "enabled" to "disabled" (and vice versa). ### Checklist - [x] 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)) ### 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) --- .../components/control_group.tsx | 134 ++++++++++-------- .../timeslider_control/components/index.scss | 7 +- .../components/play_button.tsx | 6 +- 3 files changed, 87 insertions(+), 60 deletions(-) diff --git a/examples/controls_example/public/react_controls/control_group/components/control_group.tsx b/examples/controls_example/public/react_controls/control_group/components/control_group.tsx index c8eb00b52ed9d..9608d8b082f40 100644 --- a/examples/controls_example/public/react_controls/control_group/components/control_group.tsx +++ b/examples/controls_example/public/react_controls/control_group/components/control_group.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { BehaviorSubject } from 'rxjs'; import { DndContext, @@ -98,64 +98,84 @@ export function ControlGroup({ }; }, [controlGroupApi]); + const ApplyButtonComponent = useMemo(() => { + return ( + + ); + }, [hasUnappliedSelections, applySelections]); + return ( - - + + {!isInitialized && } - setDraggingId(`${active.id}`)} - onDragEnd={onDragEnd} - onDragCancel={() => setDraggingId(null)} - sensors={sensors} - measuring={{ - droppable: { - strategy: MeasuringStrategy.BeforeDragging, - }, - }} - > - - {controlsInOrder.map(({ id, type }) => ( - controlGroupApi} - onApiAvailable={(controlApi) => { - controlsManager.setControlApi(id, controlApi); - }} - isControlGroupInitialized={isInitialized} - /> - ))} - - - {draggingId ? ( - - ) : null} - - - {!autoApplySelections && ( - - - - + + setDraggingId(`${active.id}`)} + onDragEnd={onDragEnd} + onDragCancel={() => setDraggingId(null)} + sensors={sensors} + measuring={{ + droppable: { + strategy: MeasuringStrategy.BeforeDragging, + }, + }} + > + + + {controlsInOrder.map(({ id, type }) => ( + controlGroupApi} + onApiAvailable={(controlApi) => { + controlsManager.setControlApi(id, controlApi); + }} + isControlGroupInitialized={isInitialized} + /> + ))} + + + + {draggingId ? ( + + ) : null} + + + + {isInitialized && !autoApplySelections && ( + + {hasUnappliedSelections ? ( + ApplyButtonComponent + ) : ( + + {ApplyButtonComponent} + + )} )} diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/index.scss b/examples/controls_example/public/react_controls/timeslider_control/components/index.scss index 9fb6510b1a934..de3677656848a 100644 --- a/examples/controls_example/public/react_controls/timeslider_control/components/index.scss +++ b/examples/controls_example/public/react_controls/timeslider_control/components/index.scss @@ -1,5 +1,8 @@ -.timeSlider-playToggle:enabled { - background-color: $euiColorPrimary !important; +.timeSlider-playToggle { + height: 100%; + &:enabled { + background-color: $euiColorPrimary !important; + } } .timeSlider-prependButton { diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/play_button.tsx b/examples/controls_example/public/react_controls/timeslider_control/components/play_button.tsx index c30bff5c2926d..8fe19bca8a054 100644 --- a/examples/controls_example/public/react_controls/timeslider_control/components/play_button.tsx +++ b/examples/controls_example/public/react_controls/timeslider_control/components/play_button.tsx @@ -41,7 +41,11 @@ export function PlayButton(props: Props) { /> ); return props.disablePlayButton ? ( - + {Button} ) : (