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}
) : (
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) {
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
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"
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
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/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);
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 {
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,
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.");
}
};
}
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}
);
};
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 && (