= React.memo(({ d
{i18n.DELETE_ENTRY_CONFIRMATION_CONTENT}
)}
+ {duplicateKBItem && (
+
+ {i18n.DUPLICATE_ENTRY_CONFIRMATION_CONTENT}
+
+ )}
>
);
diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.test.tsx
index e4656b10d1d31..faa4653c9beab 100644
--- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.test.tsx
+++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.test.tsx
@@ -22,6 +22,7 @@ describe('IndexEntryEditor', () => {
{ name: 'field-2', esTypes: ['text'] },
{ name: 'field-3', esTypes: ['semantic_text'] },
]),
+ getExistingIndices: jest.fn().mockResolvedValue(['index-1']),
} as unknown as DataViewsContract;
const defaultProps = {
@@ -147,4 +148,20 @@ describe('IndexEntryEditor', () => {
expect(getByRole('combobox', { name: i18n.ENTRY_FIELD_PLACEHOLDER })).toBeDisabled();
});
});
+
+ it('fetches index options and updates on selection 2', async () => {
+ (mockDataViews.getExistingIndices as jest.Mock).mockResolvedValue([]);
+ const { getByText } = render(
+
+ );
+
+ await waitFor(() => {
+ expect(mockDataViews.getExistingIndices).toHaveBeenCalled();
+ });
+
+ expect(getByText("Index doesn't exist")).toBeInTheDocument();
+ });
});
diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.tsx
index 550861bcbffd9..ff61c61ed7423 100644
--- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.tsx
+++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index_entry_editor.tsx
@@ -21,16 +21,26 @@ import React, { useCallback, useMemo } from 'react';
import { IndexEntry } from '@kbn/elastic-assistant-common';
import { DataViewsContract } from '@kbn/data-views-plugin/public';
import * as i18n from './translations';
+import { isGlobalEntry } from './helpers';
interface Props {
dataViews: DataViewsContract;
entry?: IndexEntry;
+ originalEntry?: IndexEntry;
setEntry: React.Dispatch>>;
hasManageGlobalKnowledgeBase: boolean;
}
export const IndexEntryEditor: React.FC = React.memo(
- ({ dataViews, entry, setEntry, hasManageGlobalKnowledgeBase }) => {
+ ({ dataViews, entry, setEntry, hasManageGlobalKnowledgeBase, originalEntry }) => {
+ const privateUsers = useMemo(() => {
+ const originalUsers = originalEntry?.users;
+ if (originalEntry && !isGlobalEntry(originalEntry)) {
+ return originalUsers;
+ }
+ return undefined;
+ }, [originalEntry]);
+
// Name
const setName = useCallback(
(e: React.ChangeEvent) =>
@@ -43,9 +53,9 @@ export const IndexEntryEditor: React.FC = React.memo(
(value: string) =>
setEntry((prevEntry) => ({
...prevEntry,
- users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : undefined,
+ users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : privateUsers,
})),
- [setEntry]
+ [privateUsers, setEntry]
);
const sharingOptions = [
{
@@ -96,6 +106,12 @@ export const IndexEntryEditor: React.FC = React.memo(
}));
}, [dataViews]);
+ const { value: isMissingIndex } = useAsync(async () => {
+ if (!entry?.index?.length) return false;
+
+ return !(await dataViews.getExistingIndices([entry.index])).length;
+ }, [entry?.index]);
+
const indexFields = useAsync(
async () =>
dataViews.getFieldsForWildcard({
@@ -251,11 +267,17 @@ export const IndexEntryEditor: React.FC = React.memo(
fullWidth
/>
-
+ {i18n.MISSING_INDEX_ERROR}>}
+ >
{
);
};
+const NameColumn = ({
+ entry,
+ existingIndices,
+}: {
+ entry: KnowledgeBaseEntryResponse;
+ existingIndices?: string[];
+}) => {
+ let showMissingIndexWarning = false;
+ if (existingIndices && entry.type === 'index') {
+ showMissingIndexWarning = !existingIndices.includes(entry.index);
+ }
+ return (
+ <>
+ {entry.name}
+ {showMissingIndexWarning && (
+
+
+
+ )}
+ >
+ );
+};
+
export const useKnowledgeBaseTable = () => {
const getActions = useInlineActions();
@@ -97,11 +137,13 @@ export const useKnowledgeBaseTable = () => {
const getColumns = useCallback(
({
+ existingIndices,
isDeleteEnabled,
isEditEnabled,
onDeleteActionClicked,
onEditActionClicked,
}: {
+ existingIndices?: string[];
isDeleteEnabled: (entry: KnowledgeBaseEntryResponse) => boolean;
isEditEnabled: (entry: KnowledgeBaseEntryResponse) => boolean;
onDeleteActionClicked: (entry: KnowledgeBaseEntryResponse) => void;
@@ -115,7 +157,9 @@ export const useKnowledgeBaseTable = () => {
},
{
name: i18n.COLUMN_NAME,
- render: ({ name }: KnowledgeBaseEntryResponse) => name,
+ render: (entry: KnowledgeBaseEntryResponse) => (
+
+ ),
sortable: ({ name }: KnowledgeBaseEntryResponse) => name,
width: '30%',
},
diff --git a/x-pack/packages/security-solution/upselling/messages/index.tsx b/x-pack/packages/security-solution/upselling/messages/index.tsx
index 4bda9477f13c0..1283671911402 100644
--- a/x-pack/packages/security-solution/upselling/messages/index.tsx
+++ b/x-pack/packages/security-solution/upselling/messages/index.tsx
@@ -48,8 +48,8 @@ export const ALERT_SUPPRESSION_RULE_DETAILS = i18n.translate(
);
export const UPGRADE_NOTES_MANAGEMENT_USER_FILTER = (requiredLicense: string) =>
- i18n.translate('securitySolutionPackages.noteManagement.userFilter.upsell', {
- defaultMessage: 'Upgrade to {requiredLicense} to make use of user filters',
+ i18n.translate('securitySolutionPackages.noteManagement.createdByFilter.upsell', {
+ defaultMessage: 'Upgrade to {requiredLicense} to make use of createdBy filter',
values: {
requiredLicense,
},
diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts
index 6e4ec1b69c876..76354dc882dd9 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.test.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts
@@ -851,7 +851,7 @@ describe('Action Executor', () => {
status: 'error',
retry: false,
message: `error validating action params: [param1]: expected value of type [string] but got [undefined]`,
- errorSource: TaskErrorSource.FRAMEWORK,
+ errorSource: TaskErrorSource.USER,
});
});
diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts
index a636f4b41566c..b0f9db0ef700c 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.ts
@@ -685,6 +685,17 @@ function validateAction(
try {
validatedParams = validateParams(actionType, params, validatorServices);
+ } catch (err) {
+ throw new ActionExecutionError(err.message, ActionExecutionErrorReason.Validation, {
+ actionId,
+ status: 'error',
+ message: err.message,
+ retry: !!taskInfo,
+ errorSource: TaskErrorSource.USER,
+ });
+ }
+
+ try {
validatedConfig = validateConfig(actionType, config, validatorServices);
validatedSecrets = validateSecrets(actionType, secrets, validatorServices);
if (actionType.validate?.connector) {
diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/get_summarized_alerts_query.ts b/x-pack/plugins/alerting/server/alerts_client/lib/get_summarized_alerts_query.ts
index 4f0aa0fb003df..ab3edece0becc 100644
--- a/x-pack/plugins/alerting/server/alerts_client/lib/get_summarized_alerts_query.ts
+++ b/x-pack/plugins/alerting/server/alerts_client/lib/get_summarized_alerts_query.ts
@@ -39,6 +39,7 @@ import {
import { SummarizedAlertsChunk, ScopedQueryAlerts } from '../..';
import { FormatAlert } from '../../types';
import { expandFlattenedAlert } from './format_alert';
+import { injectAnalyzeWildcard } from './inject_analyze_wildcard';
const MAX_ALERT_DOCS_TO_RETURN = 100;
enum AlertTypes {
@@ -310,9 +311,14 @@ export const getQueryByScopedQueries = ({
return;
}
- const scopedQueryFilter = generateAlertsFilterDSL({
- query: scopedQuery as AlertsFilter['query'],
- })[0] as { bool: BoolQuery };
+ const scopedQueryFilter = generateAlertsFilterDSL(
+ {
+ query: scopedQuery as AlertsFilter['query'],
+ },
+ {
+ analyzeWildcard: true,
+ }
+ )[0] as { bool: BoolQuery };
aggs[id] = {
filter: {
@@ -324,6 +330,7 @@ export const getQueryByScopedQueries = ({
aggs: {
alertId: {
top_hits: {
+ size: MAX_ALERT_DOCS_TO_RETURN,
_source: {
includes: [ALERT_UUID],
},
@@ -340,11 +347,19 @@ export const getQueryByScopedQueries = ({
};
};
-const generateAlertsFilterDSL = (alertsFilter: AlertsFilter): QueryDslQueryContainer[] => {
+const generateAlertsFilterDSL = (
+ alertsFilter: AlertsFilter,
+ options?: { analyzeWildcard?: boolean }
+): QueryDslQueryContainer[] => {
const filter: QueryDslQueryContainer[] = [];
+ const { analyzeWildcard = false } = options || {};
if (alertsFilter.query) {
- filter.push(JSON.parse(alertsFilter.query.dsl!));
+ const parsedQuery = JSON.parse(alertsFilter.query.dsl!);
+ if (analyzeWildcard) {
+ injectAnalyzeWildcard(parsedQuery);
+ }
+ filter.push(parsedQuery);
}
if (alertsFilter.timeframe) {
filter.push(
diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.test.ts b/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.test.ts
new file mode 100644
index 0000000000000..1e1db14d928ba
--- /dev/null
+++ b/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.test.ts
@@ -0,0 +1,169 @@
+/*
+ * 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 { injectAnalyzeWildcard } from './inject_analyze_wildcard';
+
+const getQuery = (query?: string) => {
+ return {
+ bool: {
+ must: [],
+ filter: [
+ {
+ bool: {
+ filter: [
+ {
+ bool: {
+ should: [
+ {
+ query_string: {
+ fields: ['kibana.alert.instance.id'],
+ query: query || '*elastic*',
+ },
+ },
+ ],
+ minimum_should_match: 1,
+ },
+ },
+ {
+ bool: {
+ should: [
+ {
+ match: {
+ 'kibana.alert.action_group': 'test',
+ },
+ },
+ ],
+ minimum_should_match: 1,
+ },
+ },
+ ],
+ },
+ },
+ ],
+ should: [],
+ must_not: [
+ {
+ match_phrase: {
+ _id: 'assdasdasd',
+ },
+ },
+ ],
+ },
+ };
+};
+describe('injectAnalyzeWildcard', () => {
+ test('should inject analyze_wildcard field', () => {
+ const query = getQuery();
+ injectAnalyzeWildcard(query);
+ expect(query).toMatchInlineSnapshot(`
+ Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "query_string": Object {
+ "analyze_wildcard": true,
+ "fields": Array [
+ "kibana.alert.instance.id",
+ ],
+ "query": "*elastic*",
+ },
+ },
+ ],
+ },
+ },
+ Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match": Object {
+ "kibana.alert.action_group": "test",
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ "must": Array [],
+ "must_not": Array [
+ Object {
+ "match_phrase": Object {
+ "_id": "assdasdasd",
+ },
+ },
+ ],
+ "should": Array [],
+ },
+ }
+ `);
+ });
+
+ test('should not inject analyze_wildcard if the query does not contain *', () => {
+ const query = getQuery('test');
+ injectAnalyzeWildcard(query);
+ expect(query).toMatchInlineSnapshot(`
+ Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "query_string": Object {
+ "fields": Array [
+ "kibana.alert.instance.id",
+ ],
+ "query": "test",
+ },
+ },
+ ],
+ },
+ },
+ Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match": Object {
+ "kibana.alert.action_group": "test",
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ "must": Array [],
+ "must_not": Array [
+ Object {
+ "match_phrase": Object {
+ "_id": "assdasdasd",
+ },
+ },
+ ],
+ "should": Array [],
+ },
+ }
+ `);
+ });
+});
diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.ts b/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.ts
new file mode 100644
index 0000000000000..58a4f89948973
--- /dev/null
+++ b/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+export const injectAnalyzeWildcard = (query: QueryDslQueryContainer): void => {
+ if (!query) {
+ return;
+ }
+
+ if (Array.isArray(query)) {
+ return query.forEach((child) => injectAnalyzeWildcard(child));
+ }
+
+ if (typeof query === 'object') {
+ Object.entries(query).forEach(([key, value]) => {
+ if (key !== 'query_string') {
+ return injectAnalyzeWildcard(value);
+ }
+
+ if (typeof value.query === 'string' && value.query.includes('*')) {
+ value.analyze_wildcard = true;
+ }
+ });
+ }
+};
diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts
index 6797b4f57d508..e377fb3209d63 100644
--- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts
+++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts
@@ -135,6 +135,21 @@ describe('MaintenanceWindowClient - update', () => {
eventEndTime: '2023-03-05T01:00:00.000Z',
})
);
+
+ expect(uiSettings.get).toHaveBeenCalledTimes(3);
+ expect(uiSettings.get.mock.calls).toMatchInlineSnapshot(`
+ Array [
+ Array [
+ "query:allowLeadingWildcards",
+ ],
+ Array [
+ "query:queryString:options",
+ ],
+ Array [
+ "courier:ignoreFilterIfFieldNotInIndex",
+ ],
+ ]
+ `);
});
it('should not regenerate all events if rrule and duration did not change', async () => {
diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.ts
index afff024b186c2..cb9d5ded1f7b2 100644
--- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.ts
+++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.ts
@@ -10,6 +10,7 @@ import Boom from '@hapi/boom';
import { buildEsQuery, Filter } from '@kbn/es-query';
import type { MaintenanceWindowClientContext } from '../../../../../common';
import { getScopedQueryErrorMessage } from '../../../../../common';
+import { getEsQueryConfig } from '../../../../lib/get_es_query_config';
import type { MaintenanceWindow } from '../../types';
import {
generateMaintenanceWindowEvents,
@@ -45,9 +46,10 @@ async function updateWithOCC(
context: MaintenanceWindowClientContext,
params: UpdateMaintenanceWindowParams
): Promise {
- const { savedObjectsClient, getModificationMetadata, logger } = context;
+ const { savedObjectsClient, getModificationMetadata, logger, uiSettings } = context;
const { id, data } = params;
const { title, enabled, duration, rRule, categoryIds, scopedQuery } = data;
+ const esQueryConfig = await getEsQueryConfig(uiSettings);
try {
updateMaintenanceWindowParamsSchema.validate(params);
@@ -62,7 +64,8 @@ async function updateWithOCC(
buildEsQuery(
undefined,
[{ query: scopedQuery.kql, language: 'kuery' }],
- scopedQuery.filters as Filter[]
+ scopedQuery.filters as Filter[],
+ esQueryConfig
)
);
scopedQueryWithGeneratedValue = {
diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx
index 0769e7a29cc59..1dc3346a72da6 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx
@@ -21,12 +21,20 @@ import {
import { ConnectorsDropdown } from './connectors_dropdown';
import { connectors, actionTypes } from './__mock__';
import { ConnectorTypes } from '../../../common/types/domain';
+import userEvent from '@testing-library/user-event';
+import { useApplicationCapabilities } from '../../common/lib/kibana';
+
+const useApplicationCapabilitiesMock = useApplicationCapabilities as jest.Mocked<
+ typeof useApplicationCapabilities
+>;
+jest.mock('../../common/lib/kibana');
describe('Connectors', () => {
let wrapper: ReactWrapper;
let appMockRender: AppMockRenderer;
const onChangeConnector = jest.fn();
const handleShowEditFlyout = jest.fn();
+ const onAddNewConnector = jest.fn();
const props: Props = {
actionTypes,
@@ -38,6 +46,7 @@ describe('Connectors', () => {
onChangeConnector,
selectedConnector: { id: 'none', type: ConnectorTypes.none },
updateConnectorDisabled: false,
+ onAddNewConnector,
};
beforeAll(() => {
@@ -104,12 +113,16 @@ describe('Connectors', () => {
});
it('shows the add connector button', () => {
- wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
- wrapper.update();
+ appMockRender.render();
- expect(
- wrapper.find('button[data-test-subj="dropdown-connector-add-connector"]').exists()
- ).toBeTruthy();
+ expect(screen.getByTestId('add-new-connector')).toBeInTheDocument();
+ });
+
+ it('shows the add connector flyout when the button is clicked', async () => {
+ appMockRender.render();
+
+ await userEvent.click(await screen.findByTestId('add-new-connector'));
+ expect(onAddNewConnector).toHaveBeenCalled();
});
it('the text of the update button is shown correctly', () => {
@@ -156,16 +169,14 @@ describe('Connectors', () => {
});
it('shows the actions permission message if the user does not have read access to actions', async () => {
- appMockRender.coreStart.application.capabilities = {
- ...appMockRender.coreStart.application.capabilities,
- actions: { save: false, show: false },
- };
+ useApplicationCapabilitiesMock().actions = { crud: false, read: false };
+
+ appMockRender.render();
- const result = appMockRender.render();
expect(
- result.getByTestId('configure-case-connector-permissions-error-msg')
+ await screen.findByTestId('configure-case-connector-permissions-error-msg')
).toBeInTheDocument();
- expect(result.queryByTestId('case-connectors-dropdown')).toBe(null);
+ expect(screen.queryByTestId('case-connectors-dropdown')).not.toBeInTheDocument();
});
it('shows the actions permission message if the user does not have access to case connector', async () => {
@@ -177,4 +188,12 @@ describe('Connectors', () => {
).toBeInTheDocument();
expect(result.queryByTestId('case-connectors-dropdown')).toBe(null);
});
+
+ it('it should hide the "Add Connector" button when the user lacks the capability to add a new connector', () => {
+ useApplicationCapabilitiesMock().actions = { crud: false, read: true };
+
+ appMockRender.render();
+
+ expect(screen.queryByTestId('add-new-connector')).not.toBeInTheDocument();
+ });
});
diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx
index b1ab16109c28f..3d742a202a0b7 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx
@@ -13,10 +13,9 @@ import {
EuiFlexItem,
EuiLink,
EuiText,
+ EuiButtonEmpty,
} from '@elastic/eui';
-import { css } from '@emotion/react';
-
import { ConnectorsDropdown } from './connectors_dropdown';
import * as i18n from './translations';
@@ -39,6 +38,7 @@ export interface Props {
onChangeConnector: (id: string) => void;
selectedConnector: { id: string; type: ConnectorTypes };
updateConnectorDisabled: boolean;
+ onAddNewConnector: () => void;
}
const ConnectorsComponent: React.FC = ({
actionTypes,
@@ -50,8 +50,10 @@ const ConnectorsComponent: React.FC = ({
onChangeConnector,
selectedConnector,
updateConnectorDisabled,
+ onAddNewConnector,
}) => {
const { actions } = useApplicationCapabilities();
+ const canSave = actions.crud;
const connector = useMemo(
() => connectors.find((c) => c.id === selectedConnector.id),
[connectors, selectedConnector.id]
@@ -95,13 +97,19 @@ const ConnectorsComponent: React.FC = ({
>
+ {i18n.ADD_CONNECTOR}
+
+ ) : null
+ }
>
@@ -113,7 +121,6 @@ const ConnectorsComponent: React.FC = ({
isLoading={isLoading}
onChange={onChangeConnector}
data-test-subj="case-connectors-dropdown"
- appendAddConnectorButton={true}
/>
) : (