Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion x-pack/plugins/lists/common/schemas/common/schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { left } from 'fp-ts/lib/Either';

import { foldLeftRight, getPaths } from '../../siem_common_deps';

import { operator_type as operatorType } from './schemas';
import { operator, operator_type as operatorType } from './schemas';

describe('Common schemas', () => {
describe('operatorType', () => {
Expand Down Expand Up @@ -60,4 +60,35 @@ describe('Common schemas', () => {
expect(keys.length).toEqual(4);
});
});

describe('operator', () => {
test('it should validate for "included"', () => {
const payload = 'included';
const decoded = operator.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it should validate for "excluded"', () => {
const payload = 'excluded';
const decoded = operator.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it should contain 2 keys', () => {
// Might seem like a weird test, but its meant to
// ensure that if operator is updated, you
// also update the operatorEnum, a workaround
// for io-ts not yet supporting enums
// https://github.com/gcanti/io-ts/issues/67
const keys = Object.keys(operator.keys);

expect(keys.length).toEqual(2);
});
});
});
4 changes: 4 additions & 0 deletions x-pack/plugins/lists/common/schemas/common/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ export type NamespaceType = t.TypeOf<typeof namespace_type>;

export const operator = t.keyof({ excluded: null, included: null });
export type Operator = t.TypeOf<typeof operator>;
export enum OperatorEnum {
INCLUDED = 'included',
EXCLUDED = 'excluded',
}

export const operator_type = t.keyof({
exists: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const antennaStyles = css`
background: ${({ theme }) => theme.eui.euiColorLightShade};
position: relative;
width: 2px;
margin: 0 12px 0 0;
&:after {
background: ${({ theme }) => theme.eui.euiColorLightShade};
content: '';
Expand All @@ -40,10 +39,6 @@ const BottomAntenna = styled(EuiFlexItem)`
}
`;

const EuiFlexItemWrapper = styled(EuiFlexItem)`
margin: 0 12px 0 0;
`;

export const RoundedBadgeAntenna: React.FC<{ type: AndOr }> = ({ type }) => (
<EuiFlexGroup
className="andBadgeContainer"
Expand All @@ -52,9 +47,9 @@ export const RoundedBadgeAntenna: React.FC<{ type: AndOr }> = ({ type }) => (
alignItems="center"
>
<TopAntenna data-test-subj="andOrBadgeBarTop" grow={1} />
<EuiFlexItemWrapper grow={false}>
<EuiFlexItem grow={false}>
<RoundedBadge type={type} />
</EuiFlexItemWrapper>
</EuiFlexItem>
<BottomAntenna data-test-subj="andOrBadgeBarBottom" grow={1} />
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* 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 { ThemeProvider } from 'styled-components';
import { mount } from 'enzyme';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';

import {
fields,
getField,
} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts';
import { FieldComponent } from './field';

describe('FieldComponent', () => {
test('it renders disabled if "isDisabled" is true', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={false}
isClearable={false}
isDisabled={true}
onChange={jest.fn()}
/>
</ThemeProvider>
);

expect(
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] input`).prop('disabled')
).toBeTruthy();
});

test('it renders loading if "isLoading" is true', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={true}
isClearable={false}
isDisabled={false}
onChange={jest.fn()}
/>
</ThemeProvider>
);
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] button`).at(0).simulate('click');
expect(
wrapper
.find(`EuiComboBoxOptionsList[data-test-subj="fieldAutocompleteComboBox-optionsList"]`)
.prop('isLoading')
).toBeTruthy();
});

test('it allows user to clear values if "isClearable" is true', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={false}
isClearable={true}
isDisabled={false}
onChange={jest.fn()}
/>
</ThemeProvider>
);

expect(
wrapper
.find(`[data-test-subj="comboBoxInput"]`)
.hasClass('euiComboBox__inputWrap-isClearable')
).toBeTruthy();
});

test('it correctly displays selected field', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={false}
isClearable={false}
isDisabled={false}
onChange={jest.fn()}
/>
</ThemeProvider>
);

expect(
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] EuiComboBoxPill`).at(0).text()
).toEqual('machine.os.raw');
});

test('it invokes "onChange" when option selected', () => {
const mockOnChange = jest.fn();
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={false}
isClearable={false}
isDisabled={false}
onChange={mockOnChange}
/>
</ThemeProvider>
);

((wrapper.find(EuiComboBox).props() as unknown) as {
onChange: (a: EuiComboBoxOptionOption[]) => void;
}).onChange([{ label: 'machine.os' }]);

expect(mockOnChange).toHaveBeenCalledWith([
{
aggregatable: true,
count: 0,
esTypes: ['text'],
name: 'machine.os',
readFromDocValues: false,
scripted: false,
searchable: true,
type: 'string',
},
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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, { useMemo, useCallback } from 'react';
import { EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui';

import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common';
import { getGenericComboBoxProps } from './helpers';
import { GetGenericComboBoxPropsReturn } from './types';

interface OperatorProps {
placeholder: string;
selectedField: IFieldType | undefined;
indexPattern: IIndexPattern | undefined;
isLoading: boolean;
isDisabled: boolean;
isClearable: boolean;
fieldInputWidth?: number;
onChange: (a: IFieldType[]) => void;
}

export const FieldComponent: React.FC<OperatorProps> = ({
placeholder,
selectedField,
indexPattern,
isLoading = false,
isDisabled = false,
isClearable = false,
fieldInputWidth = 190,
onChange,
}): JSX.Element => {
const getLabel = useCallback((field): string => field.name, []);
const optionsMemo = useMemo((): IFieldType[] => (indexPattern ? indexPattern.fields : []), [
indexPattern,
]);
const selectedOptionsMemo = useMemo((): IFieldType[] => (selectedField ? [selectedField] : []), [
selectedField,
]);
const { comboOptions, labels, selectedComboOptions } = useMemo(
(): GetGenericComboBoxPropsReturn =>
getGenericComboBoxProps<IFieldType>({
options: optionsMemo,
selectedOptions: selectedOptionsMemo,
getLabel,
}),
[optionsMemo, selectedOptionsMemo, getLabel]
);

const handleValuesChange = (newOptions: EuiComboBoxOptionOption[]): void => {
const newValues: IFieldType[] = newOptions.map(
({ label }) => optionsMemo[labels.indexOf(label)]
);
onChange(newValues);
};

return (
<EuiComboBox
placeholder={placeholder}
options={comboOptions}
selectedOptions={selectedComboOptions}
onChange={handleValuesChange}
isLoading={isLoading}
isDisabled={isDisabled}
isClearable={isClearable}
singleSelection={{ asPlainText: true }}
data-test-subj="fieldAutocompleteComboBox"
style={{ width: `${fieldInputWidth}px` }}
/>
);
};

FieldComponent.displayName = 'Field';
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 { ThemeProvider } from 'styled-components';
import { mount } from 'enzyme';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';

import { AutocompleteFieldExistsComponent } from './field_value_exists';

describe('AutocompleteFieldExistsComponent', () => {
test('it renders field disabled', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AutocompleteFieldExistsComponent placeholder="Placeholder text" />
</ThemeProvider>
);

expect(
wrapper
.find(`[data-test-subj="valuesAutocompleteComboBox existsComboxBox"] input`)
.prop('disabled')
).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 { EuiComboBox } from '@elastic/eui';

interface AutocompleteFieldExistsProps {
placeholder: string;
}

export const AutocompleteFieldExistsComponent: React.FC<AutocompleteFieldExistsProps> = ({
placeholder,
}): JSX.Element => (
<EuiComboBox
placeholder={placeholder}
options={[]}
selectedOptions={[]}
onChange={undefined}
isDisabled
data-test-subj="valuesAutocompleteComboBox existsComboxBox"
fullWidth
/>
);

AutocompleteFieldExistsComponent.displayName = 'AutocompleteFieldExists';
Loading