diff --git a/src/plugins/advanced_settings/public/management_app/index.tsx b/src/plugins/advanced_settings/public/management_app/index.tsx
deleted file mode 100644
index 53b8f9983aa2709..000000000000000
--- a/src/plugins/advanced_settings/public/management_app/index.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { HashRouter, Switch, Route } from 'react-router-dom';
-import { i18n } from '@kbn/i18n';
-import { I18nProvider } from '@kbn/i18n/react';
-import { AdvancedSettings } from './advanced_settings';
-import { ManagementSetup } from '../../../management/public';
-import { StartServicesAccessor } from '../../../../core/public';
-import { ComponentRegistry } from '../types';
-
-const title = i18n.translate('advancedSettings.advancedSettingsLabel', {
- defaultMessage: 'Advanced Settings',
-});
-const crumb = [{ text: title }];
-
-const readOnlyBadge = {
- text: i18n.translate('advancedSettings.badge.readOnly.text', {
- defaultMessage: 'Read only',
- }),
- tooltip: i18n.translate('advancedSettings.badge.readOnly.tooltip', {
- defaultMessage: 'Unable to save advanced settings',
- }),
- iconType: 'glasses',
-};
-
-export async function registerAdvSettingsMgmntApp({
- management,
- getStartServices,
- componentRegistry,
-}: {
- management: ManagementSetup;
- getStartServices: StartServicesAccessor;
- componentRegistry: ComponentRegistry['start'];
-}) {
- const kibanaSection = management.sections.getSection('kibana');
- if (!kibanaSection) {
- throw new Error('`kibana` management section not found.');
- }
-
- const advancedSettingsManagementApp = kibanaSection.registerApp({
- id: 'settings',
- title,
- order: 20,
- async mount(params) {
- params.setBreadcrumbs(crumb);
- const [
- { uiSettings, notifications, docLinks, application, chrome },
- ] = await getStartServices();
-
- const canSave = application.capabilities.advancedSettings.save as boolean;
-
- if (!canSave) {
- chrome.setBadge(readOnlyBadge);
- }
-
- ReactDOM.render(
-
-
-
-
-
-
-
-
- ,
- params.element
- );
- return () => {
- ReactDOM.unmountComponentAtNode(params.element);
- };
- },
- });
- const [{ application }] = await getStartServices();
- if (!application.capabilities.management.kibana.settings) {
- advancedSettingsManagementApp.disable();
- }
-}
diff --git a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx
new file mode 100644
index 000000000000000..df44ea45e9d01c2
--- /dev/null
+++ b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx
@@ -0,0 +1,82 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { HashRouter, Switch, Route } from 'react-router-dom';
+
+import { i18n } from '@kbn/i18n';
+import { I18nProvider } from '@kbn/i18n/react';
+import { StartServicesAccessor } from 'src/core/public';
+
+import { AdvancedSettings } from './advanced_settings';
+import { ManagementAppMountParams } from '../../../management/public';
+import { ComponentRegistry } from '../types';
+
+const title = i18n.translate('advancedSettings.advancedSettingsLabel', {
+ defaultMessage: 'Advanced Settings',
+});
+const crumb = [{ text: title }];
+
+const readOnlyBadge = {
+ text: i18n.translate('advancedSettings.badge.readOnly.text', {
+ defaultMessage: 'Read only',
+ }),
+ tooltip: i18n.translate('advancedSettings.badge.readOnly.tooltip', {
+ defaultMessage: 'Unable to save advanced settings',
+ }),
+ iconType: 'glasses',
+};
+
+export async function mountManagementSection(
+ getStartServices: StartServicesAccessor,
+ params: ManagementAppMountParams,
+ componentRegistry: ComponentRegistry['start']
+) {
+ params.setBreadcrumbs(crumb);
+ const [{ uiSettings, notifications, docLinks, application, chrome }] = await getStartServices();
+
+ const canSave = application.capabilities.advancedSettings.save as boolean;
+
+ if (!canSave) {
+ chrome.setBadge(readOnlyBadge);
+ }
+
+ ReactDOM.render(
+
+
+
+
+
+
+
+
+ ,
+ params.element
+ );
+ return () => {
+ ReactDOM.unmountComponentAtNode(params.element);
+ };
+}
diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts
index e9472fbdee0e67a..04eeff1e1f3ce33 100644
--- a/src/plugins/advanced_settings/public/plugin.ts
+++ b/src/plugins/advanced_settings/public/plugin.ts
@@ -16,21 +16,37 @@
* specific language governing permissions and limitations
* under the License.
*/
-
+import { i18n } from '@kbn/i18n';
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
+import { ManagementApp } from '../../management/public';
import { ComponentRegistry } from './component_registry';
import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types';
-import { registerAdvSettingsMgmntApp } from './management_app';
const component = new ComponentRegistry();
+const title = i18n.translate('advancedSettings.advancedSettingsLabel', {
+ defaultMessage: 'Advanced Settings',
+});
+
export class AdvancedSettingsPlugin
implements Plugin {
+ private managementApp?: ManagementApp;
public setup(core: CoreSetup, { management }: AdvancedSettingsPluginSetup) {
- registerAdvSettingsMgmntApp({
- management,
- getStartServices: core.getStartServices,
- componentRegistry: component.start,
+ const kibanaSection = management.sections.getSection('kibana');
+ if (!kibanaSection) {
+ throw new Error('`kibana` management section not found.');
+ }
+
+ this.managementApp = kibanaSection.registerApp({
+ id: 'settings',
+ title,
+ order: 20,
+ async mount(params) {
+ const { mountManagementSection } = await import(
+ './management_app/mount_management_section'
+ );
+ return mountManagementSection(core.getStartServices, params, component.start);
+ },
});
return {
@@ -39,6 +55,10 @@ export class AdvancedSettingsPlugin
}
public start(core: CoreStart) {
+ if (!core.application.capabilities.management.kibana.settings) {
+ this.managementApp!.disable();
+ }
+
return {
component: component.start,
};
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.test.ts b/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.test.ts
index 0c02b02a25af0d6..ef6eaa196b06a12 100644
--- a/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.test.ts
+++ b/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.test.ts
@@ -46,6 +46,10 @@ describe('parseInterval', () => {
validateDuration(parseInterval('5m'), 'm', 5);
});
+ test('should correctly parse 500m interval', () => {
+ validateDuration(parseInterval('500m'), 'm', 500);
+ });
+
test('should correctly parse 250ms interval', () => {
validateDuration(parseInterval('250ms'), 'ms', 250);
});
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts b/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts
index ef1d89e400b729f..857c8594720ee60 100644
--- a/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts
+++ b/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts
@@ -49,6 +49,13 @@ export function parseInterval(interval: string): moment.Duration | null {
u => Math.abs(duration.as(u)) >= 1
) as unitOfTime.Base;
+ // however if we do this fhe other way around it will also fail
+ // go from 500m to hours as this will result in infinite number (dividing 500/60 = 8.3*)
+ // so we can only do this if we are changing to smaller units
+ if (dateMath.units.indexOf(selectedUnit as any) < dateMath.units.indexOf(unit as any)) {
+ return duration;
+ }
+
return moment.duration(duration.as(selectedUnit), selectedUnit);
} catch (e) {
return null;
diff --git a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/is_json.ts b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/is_json.ts
index 5626fc80bb749af..dc8321aa07004a7 100644
--- a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/is_json.ts
+++ b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/is_json.ts
@@ -17,25 +17,6 @@
* under the License.
*/
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
import { ValidationFunc } from '../../hook_form_lib';
import { isJSON } from '../../../validators/string';
import { ERROR_CODE } from './types';
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/rule_messages.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/rule_messages.test.ts
index 8e4b5ce3c992422..bdbb6ff7d1052bc 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/rule_messages.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/rule_messages.test.ts
@@ -28,25 +28,23 @@ describe('buildRuleMessageFactory', () => {
expect(message).toEqual(expect.stringContaining('signals index: "index"'));
});
- it('joins message parts with newlines', () => {
+ it('joins message parts with spaces', () => {
const buildMessage = buildRuleMessageFactory(factoryParams);
const message = buildMessage('my message');
- const messageParts = message.split('\n');
- expect(messageParts).toContain('my message');
- expect(messageParts).toContain('name: "name"');
- expect(messageParts).toContain('id: "id"');
- expect(messageParts).toContain('rule id: "ruleId"');
- expect(messageParts).toContain('signals index: "index"');
+ expect(message).toEqual(expect.stringContaining('my message '));
+ expect(message).toEqual(expect.stringContaining(' name: "name" '));
+ expect(message).toEqual(expect.stringContaining(' id: "id" '));
+ expect(message).toEqual(expect.stringContaining(' rule id: "ruleId" '));
+ expect(message).toEqual(expect.stringContaining(' signals index: "index"'));
});
- it('joins multiple arguments with newlines', () => {
+ it('joins multiple arguments with spaces', () => {
const buildMessage = buildRuleMessageFactory(factoryParams);
const message = buildMessage('my message', 'here is more');
- const messageParts = message.split('\n');
- expect(messageParts).toContain('my message');
- expect(messageParts).toContain('here is more');
+ expect(message).toEqual(expect.stringContaining('my message '));
+ expect(message).toEqual(expect.stringContaining(' here is more'));
});
it('defaults the rule ID if not provided ', () => {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/rule_messages.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/rule_messages.ts
index d5f9d332bbcddb5..cc97a1f8a9f0b2e 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/rule_messages.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/rule_messages.ts
@@ -24,4 +24,4 @@ export const buildRuleMessageFactory = ({
`id: "${id}"`,
`rule id: "${ruleId ?? '(unknown rule id)'}"`,
`signals index: "${index}"`,
- ].join('\n');
+ ].join(' ');
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index 91905722fbca317..27074be1b5cf43b 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -126,7 +126,7 @@ export const signalRulesAlertType = ({
'Machine learning rule is missing job id and/or anomaly threshold:',
`job id: "${machineLearningJobId}"`,
`anomaly threshold: "${anomalyThreshold}"`,
- ].join('\n')
+ ].join(' ')
);
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/utils/import_timelines.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/utils/import_timelines.ts
index 5596d0c70f5ea31..f69a715f9b2c99e 100644
--- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/utils/import_timelines.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/utils/import_timelines.ts
@@ -127,7 +127,7 @@ export const saveNotes = (
existingNoteIds?: string[],
newNotes?: NoteResult[]
) => {
- return (
+ return Promise.all(
newNotes?.map(note => {
const newNote: SavedNote = {
eventId: note.eventId,
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts
index e1ac9defc858eb2..9ac53f9be609ffe 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { PolicyConfig } from '../types';
+import { PolicyConfig, ProtectionModes } from '../types';
/**
* Generate a new Policy model.
@@ -19,7 +19,7 @@ export const generatePolicy = (): PolicyConfig => {
network: true,
},
malware: {
- mode: 'prevent',
+ mode: ProtectionModes.prevent,
},
logging: {
stdout: 'debug',
@@ -44,7 +44,7 @@ export const generatePolicy = (): PolicyConfig => {
process: true,
},
malware: {
- mode: 'detect',
+ mode: ProtectionModes.detect,
},
logging: {
stdout: 'debug',
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
index 4215edb4d681089..d4f6d2457254e2f 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
@@ -123,10 +123,8 @@ export interface PolicyConfig {
process: boolean;
network: boolean;
};
- /** malware mode can be detect, prevent or prevent and notify user */
- malware: {
- mode: string;
- };
+ /** malware mode can be off, detect, prevent or prevent and notify user */
+ malware: MalwareFields;
logging: {
stdout: string;
file: string;
@@ -137,9 +135,7 @@ export interface PolicyConfig {
events: {
process: boolean;
};
- malware: {
- mode: string;
- };
+ malware: MalwareFields;
logging: {
stdout: string;
file: string;
@@ -209,6 +205,44 @@ export enum EventingFields {
network = 'network',
}
+/**
+ * Returns the keys of an object whose values meet a criteria.
+ * Ex) interface largeNestedObject = {
+ * a: {
+ * food: Foods;
+ * toiletPaper: true;
+ * };
+ * b: {
+ * food: Foods;
+ * streamingServices: Streams;
+ * };
+ * c: {};
+ * }
+ *
+ * type hasFoods = KeysByValueCriteria;
+ * The above type will be: [a, b] only, and will not include c.
+ *
+ */
+export type KeysByValueCriteria = {
+ [K in keyof O]: O[K] extends Criteria ? K : never;
+}[keyof O];
+
+/** Returns an array of the policy OSes that have a malware protection field */
+
+export type MalwareProtectionOSes = KeysByValueCriteria;
+/** Policy: Malware protection fields */
+export interface MalwareFields {
+ mode: ProtectionModes;
+}
+
+/** Policy protection mode options */
+export enum ProtectionModes {
+ detect = 'detect',
+ prevent = 'prevent',
+ preventNotify = 'preventNotify',
+ off = 'off',
+}
+
export interface GlobalState {
readonly hostList: HostListState;
readonly alertList: AlertListState;
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
index f2c79155f3c23ff..2dba301bf453763 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
@@ -35,6 +35,7 @@ import { AppAction } from '../../types';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { AgentsSummary } from './agents_summary';
import { VerticalDivider } from './vertical_divider';
+import { MalwareProtections } from './policy_forms/protections/malware';
export const PolicyDetails = React.memo(() => {
const dispatch = useDispatch<(action: AppAction) => void>();
@@ -181,6 +182,17 @@ export const PolicyDetails = React.memo(() => {
headerLeft={headerLeftContent}
headerRight={headerRightContent}
>
+
+
+
+
+
+
+
+
= React.memo(({ type, supportedOss, children, id, selectedEventing, totalEventing }) => {
+ /** Takes a react component to be put on the right corner of the card */
+ rightCorner: React.ReactNode;
+}> = React.memo(({ type, supportedOss, children, id, rightCorner }) => {
const typeTitle = () => {
return (
@@ -63,32 +62,11 @@ export const ConfigForm: React.FC<{
{supportedOss.join(', ')}
-
-
-
-
-
+ {rightCorner}
);
};
- const events = () => {
- return (
-
-
-
-
-
- );
- };
-
return (
- {events()}
-
{children}
>
}
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx
index e92e22fc97fe6e1..7bec2c4c742d2b5 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx
@@ -6,6 +6,8 @@
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui';
import { EventingCheckbox } from './checkbox';
import { OS, EventingFields } from '../../../../types';
import { usePolicyDetailsSelector } from '../../policy_hooks';
@@ -16,6 +18,9 @@ import {
import { ConfigForm } from '../config_form';
export const WindowsEventing = React.memo(() => {
+ const selected = usePolicyDetailsSelector(selectedWindowsEventing);
+ const total = usePolicyDetailsSelector(totalWindowsEventing);
+
const checkboxes = useMemo(
() => [
{
@@ -37,21 +42,43 @@ export const WindowsEventing = React.memo(() => {
);
const renderCheckboxes = () => {
- return checkboxes.map((item, index) => {
- return (
-
- );
- });
+ return (
+ <>
+
+
+
+
+
+
+ {checkboxes.map((item, index) => {
+ return (
+
+ );
+ })}
+ >
+ );
};
- const selected = usePolicyDetailsSelector(selectedWindowsEventing);
- const total = usePolicyDetailsSelector(totalWindowsEventing);
+ const collectionsEnabled = () => {
+ return (
+
+
+
+ );
+ };
return (
{
i18n.translate('xpack.endpoint.policy.details.windows', { defaultMessage: 'Windows' }),
]}
id="windowsEventingForm"
+ rightCorner={collectionsEnabled()}
children={renderCheckboxes()}
- selectedEventing={selected}
- totalEventing={total}
/>
);
});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx
new file mode 100644
index 000000000000000..66b22178607b943
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx
@@ -0,0 +1,180 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useMemo } from 'react';
+import { useDispatch } from 'react-redux';
+import styled from 'styled-components';
+import { EuiRadio, EuiSwitch, EuiTitle, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { htmlIdGenerator } from '@elastic/eui';
+import { Immutable } from '../../../../../../../common/types';
+import { OS, ProtectionModes, MalwareProtectionOSes } from '../../../../types';
+import { ConfigForm } from '../config_form';
+import { policyConfig } from '../../../../store/policy_details/selectors';
+import { usePolicyDetailsSelector } from '../../policy_hooks';
+import { clone } from '../../../../models/policy_details_config';
+
+const ProtectionRadioGroup = styled.div`
+ display: flex;
+ .policyDetailsProtectionRadio {
+ margin-right: ${props => props.theme.eui.euiSizeXXL};
+ }
+`;
+
+const OSes: Immutable = [OS.windows, OS.mac];
+const protection = 'malware';
+
+const ProtectionRadio = React.memo(({ id, label }: { id: ProtectionModes; label: string }) => {
+ const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
+ const dispatch = useDispatch();
+ // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value
+ const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode;
+
+ const handleRadioChange = useCallback(() => {
+ if (policyDetailsConfig) {
+ const newPayload = clone(policyDetailsConfig);
+ for (const os of OSes) {
+ newPayload[os][protection].mode = id;
+ }
+ dispatch({
+ type: 'userChangedPolicyConfig',
+ payload: { policyConfig: newPayload },
+ });
+ }
+ }, [dispatch, id, policyDetailsConfig]);
+
+ /**
+ * Passing an arbitrary id because EuiRadio
+ * requires an id if label is passed
+ */
+
+ return (
+ htmlIdGenerator()(), [])}
+ checked={selected === id}
+ onChange={handleRadioChange}
+ disabled={selected === ProtectionModes.off}
+ />
+ );
+});
+
+/** The Malware Protections form for policy details
+ * which will configure for all relevant OSes.
+ */
+export const MalwareProtections = React.memo(() => {
+ const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
+ const dispatch = useDispatch();
+ // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value
+ const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode;
+
+ const radios: Array<{
+ id: ProtectionModes;
+ label: string;
+ protection: 'malware';
+ }> = useMemo(() => {
+ return [
+ {
+ id: ProtectionModes.detect,
+ label: i18n.translate('xpack.endpoint.policy.details.detect', { defaultMessage: 'Detect' }),
+ protection: 'malware',
+ },
+ {
+ id: ProtectionModes.prevent,
+ label: i18n.translate('xpack.endpoint.policy.details.prevent', {
+ defaultMessage: 'Prevent',
+ }),
+ protection: 'malware',
+ },
+ {
+ id: ProtectionModes.preventNotify,
+ label: i18n.translate('xpack.endpoint.policy.details.preventAndNotify', {
+ defaultMessage: 'Prevent and notify user',
+ }),
+ protection: 'malware',
+ },
+ ];
+ }, []);
+
+ const handleSwitchChange = useCallback(
+ event => {
+ if (policyDetailsConfig) {
+ const newPayload = clone(policyDetailsConfig);
+ if (event.target.checked === false) {
+ for (const os of OSes) {
+ newPayload[os][protection].mode = ProtectionModes.off;
+ }
+ } else {
+ for (const os of OSes) {
+ newPayload[os][protection].mode = ProtectionModes.prevent;
+ }
+ }
+ dispatch({
+ type: 'userChangedPolicyConfig',
+ payload: { policyConfig: newPayload },
+ });
+ }
+ },
+ [dispatch, policyDetailsConfig]
+ );
+
+ const RadioButtons = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+ {radios.map(radio => {
+ return (
+
+ );
+ })}
+
+ >
+ );
+ };
+
+ const ProtectionSwitch = () => {
+ return (
+
+ );
+ };
+
+ return (
+
+ );
+});
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts
index 663017e2e47afde..cc4c17c5c63a379 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts
@@ -63,6 +63,10 @@ export * from './max_shingle_size_parameter';
export * from './relations_parameter';
+export * from './other_type_name_parameter';
+
+export * from './other_type_json_parameter';
+
export const PARAMETER_SERIALIZERS = [relationsSerializer, dynamicSerializer];
export const PARAMETER_DESERIALIZERS = [relationsDeserializer, dynamicDeserializer];
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_json_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_json_parameter.tsx
new file mode 100644
index 000000000000000..64e50f711a249a4
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_json_parameter.tsx
@@ -0,0 +1,92 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+
+import {
+ UseField,
+ JsonEditorField,
+ ValidationFuncArg,
+ fieldValidators,
+ FieldConfig,
+} from '../../../shared_imports';
+
+const { isJsonField } = fieldValidators;
+
+/**
+ * This is a special component that does not have an explicit entry in {@link PARAMETERS_DEFINITION}.
+ *
+ * We use it to store custom defined parameters in a field called "otherTypeJson".
+ */
+
+const fieldConfig: FieldConfig = {
+ label: i18n.translate('xpack.idxMgmt.mappingsEditor.otherTypeJsonFieldLabel', {
+ defaultMessage: 'Type Parameters JSON',
+ }),
+ defaultValue: {},
+ validations: [
+ {
+ validator: isJsonField(
+ i18n.translate(
+ 'xpack.idxMgmt.mappingsEditor.parameters.validations.otherTypeJsonInvalidJSONErrorMessage',
+ {
+ defaultMessage: 'Invalid JSON.',
+ }
+ )
+ ),
+ },
+ {
+ validator: ({ value }: ValidationFuncArg) => {
+ const json = JSON.parse(value);
+ if (Array.isArray(json)) {
+ return {
+ message: i18n.translate(
+ 'xpack.idxMgmt.mappingsEditor.parameters.validations.otherTypeJsonArrayNotAllowedErrorMessage',
+ {
+ defaultMessage: 'Arrays are not allowed.',
+ }
+ ),
+ };
+ }
+ },
+ },
+ {
+ validator: ({ value }: ValidationFuncArg) => {
+ const json = JSON.parse(value);
+ if (json.type) {
+ return {
+ code: 'ERR_CUSTOM_TYPE_OVERRIDDEN',
+ message: i18n.translate(
+ 'xpack.idxMgmt.mappingsEditor.parameters.validations.otherTypeJsonTypeFieldErrorMessage',
+ {
+ defaultMessage: 'Cannot override the "type" field.',
+ }
+ ),
+ };
+ }
+ },
+ },
+ ],
+ deserializer: (value: any) => {
+ if (value === '') {
+ return value;
+ }
+ return JSON.stringify(value, null, 2);
+ },
+ serializer: (value: string) => {
+ try {
+ return JSON.parse(value);
+ } catch (error) {
+ // swallow error and return non-parsed value;
+ return value;
+ }
+ },
+};
+
+export const OtherTypeJsonParameter = () => (
+
+);
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_name_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_name_parameter.tsx
new file mode 100644
index 000000000000000..6004e484323a141
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_name_parameter.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { i18n } from '@kbn/i18n';
+import { UseField, TextField, FieldConfig } from '../../../shared_imports';
+import { fieldValidators } from '../../../shared_imports';
+
+const { emptyField } = fieldValidators;
+
+/**
+ * This is a special component that does not have an explicit entry in {@link PARAMETERS_DEFINITION}.
+ *
+ * We use it to store the name of types unknown to the mappings editor in the "subType" path.
+ */
+
+const fieldConfig: FieldConfig = {
+ label: i18n.translate('xpack.idxMgmt.mappingsEditor.otherTypeNameFieldLabel', {
+ defaultMessage: 'Type Name',
+ }),
+ defaultValue: '',
+ validations: [
+ {
+ validator: emptyField(
+ i18n.translate(
+ 'xpack.idxMgmt.mappingsEditor.parameters.validations.otherTypeNameIsRequiredErrorMessage',
+ {
+ defaultMessage: 'The type name is required.',
+ }
+ )
+ ),
+ },
+ ],
+};
+
+export const OtherTypeNameParameter = () => (
+
+);
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx
index 60b025ce644efea..b41f35b98388511 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx
@@ -5,6 +5,7 @@
*/
import React, { useEffect, useCallback } from 'react';
import classNames from 'classnames';
+import * as _ from 'lodash';
import { i18n } from '@kbn/i18n';
@@ -31,7 +32,7 @@ import {
filterTypesForNonRootFields,
} from '../../../../lib';
import { Field, MainType, SubType, NormalizedFields, ComboBoxOption } from '../../../../types';
-import { NameParameter, TypeParameter } from '../../field_parameters';
+import { NameParameter, TypeParameter, OtherTypeNameParameter } from '../../field_parameters';
import { getParametersFormForType } from './required_parameters_forms';
const formWrapper = (props: any) => ;
@@ -155,9 +156,9 @@ export const CreateField = React.memo(function CreateFieldComponent({
},
[form, getSubTypeMeta]
);
-
const renderFormFields = useCallback(
({ type }) => {
+ const isOtherType = type === 'other';
const { subTypeOptions, subTypeLabel } = getSubTypeMeta(type);
const docLink = documentationService.getTypeDocLink(type) as string;
@@ -178,7 +179,13 @@ export const CreateField = React.memo(function CreateFieldComponent({
docLink={docLink}
/>
- {/* Field sub type (if any) */}
+ {/* Other type */}
+ {isOtherType && (
+
+
+
+ )}
+ {/* Field sub type (if any) - will never be the case if we have an "other" type */}
{subTypeOptions && (
{/* Documentation link */}
-
-
- {i18n.translate(
- 'xpack.idxMgmt.mappingsEditor.editField.typeDocumentation',
- {
- defaultMessage: '{type} documentation',
- values: {
- type: subTypeDefinition
- ? subTypeDefinition.label
- : typeDefinition.label,
- },
- }
- )}
-
-
+ {linkDocumentation && (
+
+
+ {i18n.translate(
+ 'xpack.idxMgmt.mappingsEditor.editField.typeDocumentation',
+ {
+ defaultMessage: '{type} documentation',
+ values: {
+ type: subTypeDefinition
+ ? subTypeDefinition.label
+ : typeDefinition.label,
+ },
+ }
+ )}
+
+
+ )}
{/* Field path */}
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx
index ddb808094428d9d..75a083d64b6db57 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx
@@ -17,7 +17,7 @@ import {
} from '../../../../lib';
import { TYPE_DEFINITION } from '../../../../constants';
-import { NameParameter, TypeParameter } from '../../field_parameters';
+import { NameParameter, TypeParameter, OtherTypeNameParameter } from '../../field_parameters';
import { FieldDescriptionSection } from './field_description_section';
interface Props {
@@ -80,9 +80,17 @@ export const EditFieldHeaderForm = React.memo(
/>
- {/* Field sub type (if any) */}
+ {/* Other type */}
+ {type === 'other' && (
+
+
+
+ )}
+
+ {/* Field sub type (if any) - will never be the case if we have an "other" type */}
{hasSubType && (
+ {' '}
} = {
shape: ShapeType,
dense_vector: DenseVectorType,
object: ObjectType,
+ other: OtherType,
nested: NestedType,
join: JoinType,
};
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/other_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/other_type.tsx
new file mode 100644
index 000000000000000..c403bbfb79056ef
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/other_type.tsx
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+
+import { OtherTypeJsonParameter } from '../../field_parameters';
+import { BasicParametersSection } from '../edit_field';
+
+export const OtherType = () => {
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx
index 4c1c8bc1da1143b..f274159bd6c3089 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx
@@ -16,11 +16,13 @@ import {
import { i18n } from '@kbn/i18n';
import { NormalizedField, NormalizedFields } from '../../../types';
+import { getTypeLabelFromType } from '../../../lib';
import {
TYPE_DEFINITION,
CHILD_FIELD_INDENT_SIZE,
LEFT_PADDING_SIZE_FIELD_ITEM_WRAPPER,
} from '../../../constants';
+
import { FieldsList } from './fields_list';
import { CreateField } from './create_field';
import { DeleteFieldProvider } from './delete_field_provider';
@@ -265,7 +267,7 @@ function FieldListItemComponent(
dataType: TYPE_DEFINITION[source.type].label,
},
})
- : TYPE_DEFINITION[source.type].label}
+ : getTypeLabelFromType(source.type)}
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx
index dbb8a788514bcb2..614b7cb56bef641 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx
@@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
import { SearchResult } from '../../../types';
import { TYPE_DEFINITION } from '../../../constants';
import { useDispatch } from '../../../mappings_state';
+import { getTypeLabelFromType } from '../../../lib';
import { DeleteFieldProvider } from '../fields/delete_field_provider';
interface Props {
@@ -115,7 +116,7 @@ export const SearchResultItem = React.memo(function FieldListItemFlatComponent({
dataType: TYPE_DEFINITION[source.type].label,
},
})
- : TYPE_DEFINITION[source.type].label}
+ : getTypeLabelFromType(source.type)}
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx
index f904281181c4851..4206fe8b696da78 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx
@@ -784,6 +784,20 @@ export const TYPE_DEFINITION: { [key in DataType]: DataTypeDefinition } = {
),
},
+ other: {
+ label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.otherDescription', {
+ defaultMessage: 'Other',
+ }),
+ value: 'other',
+ description: () => (
+
+
+
+ ),
+ },
};
export const MAIN_TYPES: MainType[] = [
@@ -811,6 +825,7 @@ export const MAIN_TYPES: MainType[] = [
'shape',
'text',
'token_count',
+ 'other',
];
export const MAIN_DATA_TYPE_DEFINITION: {
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/search_fields.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/search_fields.tsx
index 5a277073c5f1a34..618d106b0e7a112 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/search_fields.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/search_fields.tsx
@@ -185,8 +185,6 @@ const getSearchMetadata = (searchData: SearchData, fieldData: FieldData): Search
const score = calculateScore(metadata);
const display = getJSXdisplayFromMeta(searchData, fieldData, metadata);
- // console.log(fieldData.path, score, metadata);
-
return {
...metadata,
display,
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/serializers.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/serializers.ts
index 131d886ff05d959..6b817c829251f63 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/serializers.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/serializers.ts
@@ -45,16 +45,19 @@ const runParametersDeserializers = (field: Field): Field =>
);
export const fieldSerializer: SerializerFunc = (field: Field) => {
+ const { otherTypeJson, ...rest } = field;
+ const updatedField: Field = Boolean(otherTypeJson) ? { ...otherTypeJson, ...rest } : { ...rest };
+
// If a subType is present, use it as type for ES
- if ({}.hasOwnProperty.call(field, 'subType')) {
- field.type = field.subType as DataType;
- delete field.subType;
+ if ({}.hasOwnProperty.call(updatedField, 'subType')) {
+ updatedField.type = updatedField.subType as DataType;
+ delete updatedField.subType;
}
// Delete temp fields
- delete (field as any).useSameAnalyzerForSearch;
+ delete (updatedField as any).useSameAnalyzerForSearch;
- return sanitizeField(runParametersSerializers(field));
+ return sanitizeField(runParametersSerializers(updatedField));
};
export const fieldDeserializer: SerializerFunc = (field: Field): Field => {
@@ -70,8 +73,18 @@ export const fieldDeserializer: SerializerFunc = (field: Field): Field =>
field.type = type;
}
- (field as any).useSameAnalyzerForSearch =
- {}.hasOwnProperty.call(field, 'search_analyzer') === false;
+ if (field.type === 'other') {
+ const { type, subType, name, ...otherTypeJson } = field;
+ /**
+ * For "other" type (type we don't support through a form)
+ * we grab all the parameters and put them in the "otherTypeJson" object
+ * that we will render in a JSON editor.
+ */
+ field.otherTypeJson = otherTypeJson;
+ } else {
+ (field as any).useSameAnalyzerForSearch =
+ {}.hasOwnProperty.call(field, 'search_analyzer') === false;
+ }
return runParametersDeserializers(field);
};
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts
index 337554ab5fa5a1f..cece26618ced87f 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts
@@ -25,6 +25,7 @@ import {
PARAMETERS_DEFINITION,
TYPE_NOT_ALLOWED_MULTIFIELD,
TYPE_ONLY_ALLOWED_AT_ROOT_LEVEL,
+ TYPE_DEFINITION,
} from '../constants';
import { State } from '../reducer';
@@ -71,6 +72,9 @@ export const getFieldMeta = (field: Field, isMultiField?: boolean): FieldMeta =>
};
};
+export const getTypeLabelFromType = (type: DataType) =>
+ TYPE_DEFINITION[type] ? TYPE_DEFINITION[type].label : `${TYPE_DEFINITION.other.label}: ${type}`;
+
export const getFieldConfig = (param: ParameterName, prop?: string): FieldConfig => {
if (prop !== undefined) {
if (
@@ -122,7 +126,7 @@ const replaceAliasPathByAliasId = (
};
export const getMainTypeFromSubType = (subType: SubType): MainType =>
- SUB_TYPE_MAP_TO_MAIN[subType] as MainType;
+ (SUB_TYPE_MAP_TO_MAIN[subType] ?? 'other') as MainType;
/**
* In order to better work with the recursive pattern of the mappings `properties`, this method flatten the fields
@@ -287,7 +291,9 @@ export const deNormalize = ({ rootLevelFields, byId, aliases }: NormalizedFields
const { source, childFields, childFieldsName } = serializedFieldsById[id];
const { name, ...normalizedField } = source;
const field: Omit = normalizedField;
+
to[name] = field;
+
if (childFields) {
field[childFieldsName!] = {};
return deNormalizePaths(childFields, field[childFieldsName!]);
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types.ts
index dbbffe5a0bd3160..5b18af68ed55b93 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types.ts
@@ -56,7 +56,12 @@ export type MainType =
| 'date_nanos'
| 'geo_point'
| 'geo_shape'
- | 'token_count';
+ | 'token_count'
+ /**
+ * 'other' is a special type that only exists inside of MappingsEditor as a placeholder
+ * for undocumented field types.
+ */
+ | 'other';
export type SubType = NumericType | RangeType;
@@ -156,6 +161,10 @@ interface FieldBasic {
subType?: SubType;
properties?: { [key: string]: Omit };
fields?: { [key: string]: Omit };
+
+ // other* exist together as a holder of types that the mappings editor does not yet know about but
+ // enables the user to create mappings with them.
+ otherTypeJson?: GenericObject;
}
type FieldParams = {
diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx
index 0909a3c2ed569f9..cd3ba43c3607c77 100644
--- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx
+++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx
@@ -89,7 +89,7 @@ export const Expressions: React.FC = props => {
const defaultExpression = useMemo(
() => ({
- aggType: AGGREGATION_TYPES.MAX,
+ aggType: AGGREGATION_TYPES.AVERAGE,
comparator: '>',
threshold: [],
timeSize: 1,
diff --git a/x-pack/plugins/infra/public/compose_libs.ts b/x-pack/plugins/infra/public/compose_libs.ts
new file mode 100644
index 000000000000000..debd83f43d52cf6
--- /dev/null
+++ b/x-pack/plugins/infra/public/compose_libs.ts
@@ -0,0 +1,99 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import ApolloClient from 'apollo-client';
+import { ApolloLink } from 'apollo-link';
+import { createHttpLink } from 'apollo-link-http';
+import { withClientState } from 'apollo-link-state';
+import { CoreStart, HttpFetchOptions } from 'src/core/public';
+import { InfraFrontendLibs } from './lib/lib';
+import introspectionQueryResultData from './graphql/introspection.json';
+import { InfraKibanaObservableApiAdapter } from './lib/adapters/observable_api/kibana_observable_api';
+
+export function composeLibs(core: CoreStart) {
+ const cache = new InMemoryCache({
+ addTypename: false,
+ fragmentMatcher: new IntrospectionFragmentMatcher({
+ introspectionQueryResultData,
+ }),
+ });
+
+ const observableApi = new InfraKibanaObservableApiAdapter({
+ basePath: core.http.basePath.get(),
+ });
+
+ const wrappedFetch = (path: string, options: HttpFetchOptions) => {
+ return new Promise(async (resolve, reject) => {
+ // core.http.fetch isn't 100% compatible with the Fetch API and will
+ // throw Errors on 401s. This top level try / catch handles those scenarios.
+ try {
+ core.http
+ .fetch(path, {
+ ...options,
+ // Set headers to undefined due to this bug: https://github.com/apollographql/apollo-link/issues/249,
+ // Apollo will try to set a "content-type" header which will conflict with the "Content-Type" header that
+ // core.http.fetch correctly sets.
+ headers: undefined,
+ asResponse: true,
+ })
+ .then(res => {
+ if (!res.response) {
+ return reject();
+ }
+ // core.http.fetch will parse the Response and set a body before handing it back. As such .text() / .json()
+ // will have already been called on the Response instance. However, Apollo will also want to call
+ // .text() / .json() on the instance, as it expects the raw Response instance, rather than core's wrapper.
+ // .text() / .json() can only be called once, and an Error will be thrown if those methods are accessed again.
+ // This hacks around that by setting up a new .text() method that will restringify the JSON response we already have.
+ // This does result in an extra stringify / parse cycle, which isn't ideal, but as we only have a few endpoints left using
+ // GraphQL this shouldn't create excessive overhead.
+ // Ref: https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http/src/httpLink.ts#L134
+ // and
+ // https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http-common/src/index.ts#L125
+ return resolve({
+ ...res.response,
+ text: () => {
+ return new Promise(async (resolveText, rejectText) => {
+ if (res.body) {
+ return resolveText(JSON.stringify(res.body));
+ } else {
+ return rejectText();
+ }
+ });
+ },
+ });
+ });
+ } catch (error) {
+ reject(error);
+ }
+ });
+ };
+
+ const HttpLink = createHttpLink({
+ fetch: wrappedFetch,
+ uri: `/api/infra/graphql`,
+ });
+
+ const graphQLOptions = {
+ cache,
+ link: ApolloLink.from([
+ withClientState({
+ cache,
+ resolvers: {},
+ }),
+ HttpLink,
+ ]),
+ };
+
+ const apolloClient = new ApolloClient(graphQLOptions);
+
+ const libs: InfraFrontendLibs = {
+ apolloClient,
+ observableApi,
+ };
+ return libs;
+}
diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts
index 15796f35856bdbb..3b6647b9bfbbeb6 100644
--- a/x-pack/plugins/infra/public/plugin.ts
+++ b/x-pack/plugins/infra/public/plugin.ts
@@ -12,23 +12,14 @@ import {
PluginInitializerContext,
AppMountParameters,
} from 'kibana/public';
-import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
-import ApolloClient from 'apollo-client';
-import { ApolloLink } from 'apollo-link';
-import { createHttpLink } from 'apollo-link-http';
-import { withClientState } from 'apollo-link-state';
-import { HttpFetchOptions } from 'src/core/public';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils';
-import { InfraFrontendLibs } from './lib/lib';
-import introspectionQueryResultData from './graphql/introspection.json';
-import { InfraKibanaObservableApiAdapter } from './lib/adapters/observable_api/kibana_observable_api';
import { registerStartSingleton } from './legacy_singletons';
import { registerFeatures } from './register_feature';
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public';
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
import { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public';
-import { LogsRouter, MetricsRouter } from './routers';
+
import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public';
import { getAlertType } from './components/alerting/metrics/metric_threshold_alert_type';
@@ -75,9 +66,10 @@ export class Plugin
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
const plugins = getMergedPlugins(pluginsSetup, pluginsStart as ClientPluginsStart);
- const { startApp } = await import('./apps/start_app');
+ const { startApp, composeLibs, LogsRouter } = await this.downloadAssets();
+
return startApp(
- this.composeLibs(coreStart, plugins),
+ composeLibs(coreStart),
coreStart,
plugins,
params,
@@ -99,9 +91,10 @@ export class Plugin
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
const plugins = getMergedPlugins(pluginsSetup, pluginsStart as ClientPluginsStart);
- const { startApp } = await import('./apps/start_app');
+ const { startApp, composeLibs, MetricsRouter } = await this.downloadAssets();
+
return startApp(
- this.composeLibs(coreStart, plugins),
+ composeLibs(coreStart),
coreStart,
plugins,
params,
@@ -129,87 +122,18 @@ export class Plugin
registerStartSingleton(core);
}
- composeLibs(core: CoreStart, plugins: ClientPluginsStart) {
- const cache = new InMemoryCache({
- addTypename: false,
- fragmentMatcher: new IntrospectionFragmentMatcher({
- introspectionQueryResultData,
- }),
- });
-
- const observableApi = new InfraKibanaObservableApiAdapter({
- basePath: core.http.basePath.get(),
- });
-
- const wrappedFetch = (path: string, options: HttpFetchOptions) => {
- return new Promise(async (resolve, reject) => {
- // core.http.fetch isn't 100% compatible with the Fetch API and will
- // throw Errors on 401s. This top level try / catch handles those scenarios.
- try {
- core.http
- .fetch(path, {
- ...options,
- // Set headers to undefined due to this bug: https://github.com/apollographql/apollo-link/issues/249,
- // Apollo will try to set a "content-type" header which will conflict with the "Content-Type" header that
- // core.http.fetch correctly sets.
- headers: undefined,
- asResponse: true,
- })
- .then(res => {
- if (!res.response) {
- return reject();
- }
- // core.http.fetch will parse the Response and set a body before handing it back. As such .text() / .json()
- // will have already been called on the Response instance. However, Apollo will also want to call
- // .text() / .json() on the instance, as it expects the raw Response instance, rather than core's wrapper.
- // .text() / .json() can only be called once, and an Error will be thrown if those methods are accessed again.
- // This hacks around that by setting up a new .text() method that will restringify the JSON response we already have.
- // This does result in an extra stringify / parse cycle, which isn't ideal, but as we only have a few endpoints left using
- // GraphQL this shouldn't create excessive overhead.
- // Ref: https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http/src/httpLink.ts#L134
- // and
- // https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http-common/src/index.ts#L125
- return resolve({
- ...res.response,
- text: () => {
- return new Promise(async (resolveText, rejectText) => {
- if (res.body) {
- return resolveText(JSON.stringify(res.body));
- } else {
- return rejectText();
- }
- });
- },
- });
- });
- } catch (error) {
- reject(error);
- }
- });
- };
-
- const HttpLink = createHttpLink({
- fetch: wrappedFetch,
- uri: `/api/infra/graphql`,
- });
-
- const graphQLOptions = {
- cache,
- link: ApolloLink.from([
- withClientState({
- cache,
- resolvers: {},
- }),
- HttpLink,
- ]),
- };
-
- const apolloClient = new ApolloClient(graphQLOptions);
-
- const libs: InfraFrontendLibs = {
- apolloClient,
- observableApi,
+ private async downloadAssets() {
+ const [{ startApp }, { composeLibs }, { LogsRouter, MetricsRouter }] = await Promise.all([
+ import('./apps/start_app'),
+ import('./compose_libs'),
+ import('./routers'),
+ ]);
+
+ return {
+ startApp,
+ composeLibs,
+ LogsRouter,
+ MetricsRouter,
};
- return libs;
}
}
diff --git a/x-pack/test/accessibility/apps/grok_debugger.ts b/x-pack/test/accessibility/apps/grok_debugger.ts
new file mode 100644
index 000000000000000..0b052d39a4db852
--- /dev/null
+++ b/x-pack/test/accessibility/apps/grok_debugger.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FtrProviderContext } from '../ftr_provider_context';
+
+export default function({ getService, getPageObjects }: FtrProviderContext) {
+ const PageObjects = getPageObjects(['common', 'security']);
+ const a11y = getService('a11y');
+ const grokDebugger = getService('grokDebugger');
+
+ // this test is failing as there is a violation https://github.com/elastic/kibana/issues/62102
+ describe.skip('Dev tools grok debugger', () => {
+ before(async () => {
+ await PageObjects.common.navigateToApp('grokDebugger');
+ await grokDebugger.assertExists();
+ });
+
+ it('Dev tools grok debugger set input', async () => {
+ await grokDebugger.setEventInput('SegerCommaBob');
+ await a11y.testAppSnapshot();
+ });
+
+ it('Dev tools grok debugger set pattern', async () => {
+ await grokDebugger.setPatternInput('%{USERNAME:u}');
+ await a11y.testAppSnapshot();
+ });
+
+ it('Dev tools grok debugger simulate', async () => {
+ await grokDebugger.clickSimulate();
+ await a11y.testAppSnapshot();
+ });
+ });
+}
diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts
index a9ac7c71d3e79ef..c8a31ab4ceba849 100644
--- a/x-pack/test/accessibility/config.ts
+++ b/x-pack/test/accessibility/config.ts
@@ -13,7 +13,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
return {
...functionalConfig.getAll(),
- testFiles: [require.resolve('./apps/login_page')],
+ testFiles: [require.resolve('./apps/login_page'), require.resolve('./apps/grok_debugger')],
pageObjects,
services,