mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
8d1ae019ae
commit
39102c33e4
28 changed files with 814 additions and 451 deletions
|
@ -19,7 +19,6 @@ export function loadInitialState() {
|
|||
[restricted.id]: restricted,
|
||||
},
|
||||
layers: {},
|
||||
showEmptyFields: false,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
|
49
x-pack/plugins/lens/public/indexpattern_datasource/__snapshots__/no_fields_callout.test.tsx.snap
generated
Normal file
49
x-pack/plugins/lens/public/indexpattern_datasource/__snapshots__/no_fields_callout.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NoFieldCallout renders properly for index with no fields 1`] = `
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
size="s"
|
||||
title="No fields exist in this index pattern."
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`NoFieldCallout renders properly when affected by field filter 1`] = `
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
size="s"
|
||||
title="No fields match the selected filters."
|
||||
>
|
||||
<strong>
|
||||
Try:
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
Using different field filters
|
||||
</li>
|
||||
</ul>
|
||||
</EuiCallOut>
|
||||
`;
|
||||
|
||||
exports[`NoFieldCallout renders properly when affected by field filters, global filter and timerange 1`] = `
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
size="s"
|
||||
title="No fields match the selected filters."
|
||||
>
|
||||
<strong>
|
||||
Try:
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
Extending the time range
|
||||
</li>
|
||||
<li>
|
||||
Using different field filters
|
||||
</li>
|
||||
<li>
|
||||
Changing the global filters
|
||||
</li>
|
||||
</ul>
|
||||
</EuiCallOut>
|
||||
`;
|
|
@ -1,2 +1 @@
|
|||
@import 'datapanel';
|
||||
@import 'field_item';
|
||||
|
|
|
@ -16,10 +16,6 @@
|
|||
line-height: $euiSizeXXL;
|
||||
}
|
||||
|
||||
.lnsInnerIndexPatternDataPanel__filterWrapper {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Don't cut off the shadow of the field items
|
||||
*/
|
||||
|
@ -41,11 +37,9 @@
|
|||
right: $euiSizeXS; /* 1 */
|
||||
}
|
||||
|
||||
.lnsInnerIndexPatternDataPanel__filterButton {
|
||||
width: 100%;
|
||||
color: $euiColorPrimary;
|
||||
padding-left: $euiSizeS;
|
||||
padding-right: $euiSizeS;
|
||||
.lnsInnerIndexPatternDataPanel__fieldItems {
|
||||
// Quick fix for making sure the shadow and focus rings are visible outside the accordion bounds
|
||||
padding: $euiSizeXS $euiSizeXS 0;
|
||||
}
|
||||
|
||||
.lnsInnerIndexPatternDataPanel__textField {
|
||||
|
@ -54,7 +48,9 @@
|
|||
}
|
||||
|
||||
.lnsInnerIndexPatternDataPanel__filterType {
|
||||
font-size: $euiFontSizeS;
|
||||
padding: $euiSizeS;
|
||||
border-bottom: 1px solid $euiColorLightestShade;
|
||||
}
|
||||
|
||||
.lnsInnerIndexPatternDataPanel__filterTypeInner {
|
|
@ -9,19 +9,19 @@ import { createMockedDragDropContext } from './mocks';
|
|||
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
|
||||
import { InnerIndexPatternDataPanel, IndexPatternDataPanel, MemoizedDataPanel } from './datapanel';
|
||||
import { FieldItem } from './field_item';
|
||||
import { NoFieldsCallout } from './no_fields_callout';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { IndexPatternPrivateState } from './types';
|
||||
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { ChangeIndexPattern } from './change_indexpattern';
|
||||
import { EuiProgress } from '@elastic/eui';
|
||||
import { EuiProgress, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { documentField } from './document_field';
|
||||
|
||||
const initialState: IndexPatternPrivateState = {
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
@ -229,8 +229,6 @@ describe('IndexPattern Data Panel', () => {
|
|||
},
|
||||
query: { query: '', language: 'lucene' },
|
||||
filters: [],
|
||||
showEmptyFields: false,
|
||||
onToggleEmptyFields: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -303,7 +301,6 @@ describe('IndexPattern Data Panel', () => {
|
|||
state: {
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
showEmptyFields: false,
|
||||
currentIndexPatternId: 'a',
|
||||
indexPatterns: {
|
||||
a: { id: 'a', title: 'aaa', timeFieldName: 'atime', fields: [] },
|
||||
|
@ -534,117 +531,11 @@ describe('IndexPattern Data Panel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('while showing empty fields', () => {
|
||||
it('should list all supported fields in the pattern sorted alphabetically', async () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<InnerIndexPatternDataPanel {...defaultProps} showEmptyFields={true} />
|
||||
);
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'Records',
|
||||
'bytes',
|
||||
'client',
|
||||
'memory',
|
||||
'source',
|
||||
'timestamp',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter down by name', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<InnerIndexPatternDataPanel {...defaultProps} showEmptyFields={true} />
|
||||
);
|
||||
|
||||
act(() => {
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFieldSearch"]').prop('onChange')!({
|
||||
target: { value: 'mem' },
|
||||
} as ChangeEvent<HTMLInputElement>);
|
||||
});
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'memory',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter down by type', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<InnerIndexPatternDataPanel {...defaultProps} showEmptyFields={true} />
|
||||
);
|
||||
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click');
|
||||
|
||||
wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click');
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'bytes',
|
||||
'memory',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should toggle type if clicked again', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<InnerIndexPatternDataPanel {...defaultProps} showEmptyFields={true} />
|
||||
);
|
||||
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click');
|
||||
|
||||
wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click');
|
||||
wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click');
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'Records',
|
||||
'bytes',
|
||||
'client',
|
||||
'memory',
|
||||
'source',
|
||||
'timestamp',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter down by type and by name', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<InnerIndexPatternDataPanel {...defaultProps} showEmptyFields={true} />
|
||||
);
|
||||
|
||||
act(() => {
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFieldSearch"]').prop('onChange')!({
|
||||
target: { value: 'mem' },
|
||||
} as ChangeEvent<HTMLInputElement>);
|
||||
});
|
||||
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click');
|
||||
|
||||
wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click');
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'memory',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filtering out empty fields', () => {
|
||||
let emptyFieldsTestProps: typeof defaultProps;
|
||||
|
||||
describe('displaying field list', () => {
|
||||
let props: Parameters<typeof InnerIndexPatternDataPanel>[0];
|
||||
beforeEach(() => {
|
||||
emptyFieldsTestProps = {
|
||||
props = {
|
||||
...defaultProps,
|
||||
indexPatterns: {
|
||||
...defaultProps.indexPatterns,
|
||||
'1': {
|
||||
...defaultProps.indexPatterns['1'],
|
||||
fields: defaultProps.indexPatterns['1'].fields.map((field) => ({
|
||||
...field,
|
||||
exists: field.type === 'number',
|
||||
})),
|
||||
},
|
||||
},
|
||||
onToggleEmptyFields: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
it('should list all supported fields in the pattern sorted alphabetically', async () => {
|
||||
const props = {
|
||||
...emptyFieldsTestProps,
|
||||
existingFields: {
|
||||
idx1: {
|
||||
bytes: true,
|
||||
|
@ -652,41 +543,145 @@ describe('IndexPattern Data Panel', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const wrapper = shallowWithIntl(<InnerIndexPatternDataPanel {...props} />);
|
||||
});
|
||||
it('should list all supported fields in the pattern sorted alphabetically in groups', async () => {
|
||||
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
|
||||
expect(wrapper.find(FieldItem).first().prop('field').name).toEqual('Records');
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="lnsIndexPatternAvailableFields"]')
|
||||
.find(FieldItem)
|
||||
.map((fieldItem) => fieldItem.prop('field').name)
|
||||
).toEqual(['bytes', 'memory']);
|
||||
wrapper
|
||||
.find('[data-test-subj="lnsIndexPatternEmptyFields"]')
|
||||
.find('button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="lnsIndexPatternEmptyFields"]')
|
||||
.find(FieldItem)
|
||||
.map((fieldItem) => fieldItem.prop('field').name)
|
||||
).toEqual(['client', 'source', 'timestamp']);
|
||||
});
|
||||
|
||||
it('should display NoFieldsCallout when all fields are empty', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<InnerIndexPatternDataPanel {...defaultProps} existingFields={{ idx1: {} }} />
|
||||
);
|
||||
expect(wrapper.find(NoFieldsCallout).length).toEqual(1);
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="lnsIndexPatternAvailableFields"]')
|
||||
.find(FieldItem)
|
||||
.map((fieldItem) => fieldItem.prop('field').name)
|
||||
).toEqual([]);
|
||||
wrapper
|
||||
.find('[data-test-subj="lnsIndexPatternEmptyFields"]')
|
||||
.find('button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="lnsIndexPatternEmptyFields"]')
|
||||
.find(FieldItem)
|
||||
.map((fieldItem) => fieldItem.prop('field').name)
|
||||
).toEqual(['bytes', 'client', 'memory', 'source', 'timestamp']);
|
||||
});
|
||||
|
||||
it('should display spinner for available fields accordion if existing fields are not loaded yet', async () => {
|
||||
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...defaultProps} />);
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternAvailableFields"]').find(EuiLoadingSpinner)
|
||||
.length
|
||||
).toEqual(1);
|
||||
wrapper.setProps({ existingFields: { idx1: {} } });
|
||||
expect(wrapper.find(NoFieldsCallout).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should filter down by name', () => {
|
||||
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
|
||||
act(() => {
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFieldSearch"]').prop('onChange')!({
|
||||
target: { value: 'me' },
|
||||
} as ChangeEvent<HTMLInputElement>);
|
||||
});
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="lnsIndexPatternEmptyFields"]')
|
||||
.find('button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'memory',
|
||||
'timestamp',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter down by type', () => {
|
||||
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
|
||||
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click');
|
||||
|
||||
wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click');
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'Records',
|
||||
'bytes',
|
||||
'memory',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter down by name', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<InnerIndexPatternDataPanel {...emptyFieldsTestProps} showEmptyFields={true} />
|
||||
);
|
||||
it('should display no fields in groups when filtered by type Record', () => {
|
||||
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
|
||||
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click');
|
||||
|
||||
wrapper.find('[data-test-subj="typeFilter-document"]').first().simulate('click');
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'Records',
|
||||
]);
|
||||
expect(wrapper.find(NoFieldsCallout).length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should toggle type if clicked again', () => {
|
||||
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click');
|
||||
|
||||
wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click');
|
||||
wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click');
|
||||
wrapper
|
||||
.find('[data-test-subj="lnsIndexPatternEmptyFields"]')
|
||||
.find('button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'Records',
|
||||
'bytes',
|
||||
'memory',
|
||||
'client',
|
||||
'source',
|
||||
'timestamp',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter down by type and by name', () => {
|
||||
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...props} />);
|
||||
act(() => {
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFieldSearch"]').prop('onChange')!({
|
||||
target: { value: 'mem' },
|
||||
target: { value: 'me' },
|
||||
} as ChangeEvent<HTMLInputElement>);
|
||||
});
|
||||
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click');
|
||||
|
||||
wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click');
|
||||
|
||||
expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([
|
||||
'memory',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should allow removing the filter for data', () => {
|
||||
const wrapper = mountWithIntl(<InnerIndexPatternDataPanel {...emptyFieldsTestProps} />);
|
||||
|
||||
wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click');
|
||||
|
||||
wrapper.find('[data-test-subj="lnsEmptyFilter"]').first().prop('onChange')!(
|
||||
{} as ChangeEvent
|
||||
);
|
||||
|
||||
expect(emptyFieldsTestProps.onToggleEmptyFields).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,26 +4,21 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { uniq, indexBy } from 'lodash';
|
||||
import React, { useState, useEffect, memo, useCallback } from 'react';
|
||||
import './datapanel.scss';
|
||||
import { uniq, indexBy, groupBy, throttle } from 'lodash';
|
||||
import React, { useState, useEffect, memo, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
// @ts-ignore
|
||||
EuiHighlight,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiContextMenuPanel,
|
||||
EuiContextMenuItem,
|
||||
EuiContextMenuPanelProps,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiPopoverFooter,
|
||||
EuiCallOut,
|
||||
EuiFormControlLayout,
|
||||
EuiSwitch,
|
||||
EuiFacetButton,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
EuiFormLabel,
|
||||
EuiFilterGroup,
|
||||
EuiFilterButton,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
@ -31,6 +26,7 @@ import { DataPublicPluginStart } from 'src/plugins/data/public';
|
|||
import { DatasourceDataPanelProps, DataType, StateSetter } from '../types';
|
||||
import { ChildDragDropProvider, DragContextState } from '../drag_drop';
|
||||
import { FieldItem } from './field_item';
|
||||
import { NoFieldsCallout } from './no_fields_callout';
|
||||
import {
|
||||
IndexPattern,
|
||||
IndexPatternPrivateState,
|
||||
|
@ -41,6 +37,7 @@ import { trackUiEvent } from '../lens_ui_telemetry';
|
|||
import { syncExistingFields } from './loader';
|
||||
import { fieldExists } from './pure_helpers';
|
||||
import { Loader } from '../loader';
|
||||
import { FieldsAccordion } from './fields_accordion';
|
||||
import { esQuery, IIndexPattern } from '../../../../../src/plugins/data/public';
|
||||
|
||||
export type Props = DatasourceDataPanelProps<IndexPatternPrivateState> & {
|
||||
|
@ -87,21 +84,9 @@ export function IndexPatternDataPanel({
|
|||
changeIndexPattern,
|
||||
}: Props) {
|
||||
const { indexPatternRefs, indexPatterns, currentIndexPatternId } = state;
|
||||
|
||||
const onChangeIndexPattern = useCallback(
|
||||
(id: string) => changeIndexPattern(id, state, setState),
|
||||
[state, setState]
|
||||
);
|
||||
|
||||
const onToggleEmptyFields = useCallback(
|
||||
(showEmptyFields?: boolean) => {
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
showEmptyFields:
|
||||
showEmptyFields === undefined ? !prevState.showEmptyFields : showEmptyFields,
|
||||
}));
|
||||
},
|
||||
[setState]
|
||||
[state, setState, changeIndexPattern]
|
||||
);
|
||||
|
||||
const indexPatternList = uniq(
|
||||
|
@ -179,8 +164,6 @@ export function IndexPatternDataPanel({
|
|||
dateRange={dateRange}
|
||||
filters={filters}
|
||||
dragDropContext={dragDropContext}
|
||||
showEmptyFields={state.showEmptyFields}
|
||||
onToggleEmptyFields={onToggleEmptyFields}
|
||||
core={core}
|
||||
data={data}
|
||||
onChangeIndexPattern={onChangeIndexPattern}
|
||||
|
@ -195,8 +178,26 @@ interface DataPanelState {
|
|||
nameFilter: string;
|
||||
typeFilter: DataType[];
|
||||
isTypeFilterOpen: boolean;
|
||||
isAvailableAccordionOpen: boolean;
|
||||
isEmptyAccordionOpen: boolean;
|
||||
}
|
||||
|
||||
export interface FieldsGroup {
|
||||
specialFields: IndexPatternField[];
|
||||
availableFields: IndexPatternField[];
|
||||
emptyFields: IndexPatternField[];
|
||||
}
|
||||
|
||||
const defaultFieldGroups = {
|
||||
specialFields: [],
|
||||
availableFields: [],
|
||||
emptyFields: [],
|
||||
};
|
||||
|
||||
const fieldFiltersLabel = i18n.translate('xpack.lens.indexPatterns.fieldFiltersLabel', {
|
||||
defaultMessage: 'Field filters',
|
||||
});
|
||||
|
||||
export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
||||
currentIndexPatternId,
|
||||
indexPatternRefs,
|
||||
|
@ -206,8 +207,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
filters,
|
||||
dragDropContext,
|
||||
onChangeIndexPattern,
|
||||
showEmptyFields,
|
||||
onToggleEmptyFields,
|
||||
core,
|
||||
data,
|
||||
existingFields,
|
||||
|
@ -217,8 +216,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
indexPatternRefs: IndexPatternRef[];
|
||||
indexPatterns: Record<string, IndexPattern>;
|
||||
dragDropContext: DragContextState;
|
||||
showEmptyFields: boolean;
|
||||
onToggleEmptyFields: (showEmptyFields?: boolean) => void;
|
||||
onChangeIndexPattern: (newId: string) => void;
|
||||
existingFields: IndexPatternPrivateState['existingFields'];
|
||||
}) {
|
||||
|
@ -226,79 +223,158 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
nameFilter: '',
|
||||
typeFilter: [],
|
||||
isTypeFilterOpen: false,
|
||||
isAvailableAccordionOpen: true,
|
||||
isEmptyAccordionOpen: false,
|
||||
});
|
||||
const [pageSize, setPageSize] = useState(PAGINATION_SIZE);
|
||||
const [scrollContainer, setScrollContainer] = useState<Element | undefined>(undefined);
|
||||
const currentIndexPattern = indexPatterns[currentIndexPatternId];
|
||||
const allFields = currentIndexPattern.fields;
|
||||
const fieldByName = indexBy(allFields, 'name');
|
||||
const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '', typeFilter: [] }));
|
||||
|
||||
const lazyScroll = () => {
|
||||
if (scrollContainer) {
|
||||
const nearBottom =
|
||||
scrollContainer.scrollTop + scrollContainer.clientHeight >
|
||||
scrollContainer.scrollHeight * 0.9;
|
||||
if (nearBottom) {
|
||||
setPageSize(Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, allFields.length)));
|
||||
}
|
||||
}
|
||||
};
|
||||
const hasSyncedExistingFields = existingFields[currentIndexPattern.title];
|
||||
const availableFieldTypes = uniq(allFields.map(({ type }) => type)).filter(
|
||||
(type) => type in fieldTypeNames
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Reset the scroll if we have made material changes to the field list
|
||||
if (scrollContainer) {
|
||||
scrollContainer.scrollTop = 0;
|
||||
setPageSize(PAGINATION_SIZE);
|
||||
lazyScroll();
|
||||
}
|
||||
}, [localState.nameFilter, localState.typeFilter, currentIndexPatternId, showEmptyFields]);
|
||||
}, [localState.nameFilter, localState.typeFilter, currentIndexPatternId, scrollContainer]);
|
||||
|
||||
const availableFieldTypes = uniq(allFields.map(({ type }) => type)).filter(
|
||||
(type) => type in fieldTypeNames
|
||||
);
|
||||
const fieldGroups: FieldsGroup = useMemo(() => {
|
||||
const containsData = (field: IndexPatternField) => {
|
||||
const fieldByName = indexBy(allFields, 'name');
|
||||
const overallField = fieldByName[field.name];
|
||||
|
||||
const displayedFields = allFields.filter((field) => {
|
||||
if (!supportedFieldTypes.has(field.type)) {
|
||||
return false;
|
||||
return (
|
||||
overallField && fieldExists(existingFields, currentIndexPattern.title, overallField.name)
|
||||
);
|
||||
};
|
||||
|
||||
const allSupportedTypesFields = allFields.filter((field) =>
|
||||
supportedFieldTypes.has(field.type)
|
||||
);
|
||||
const sorted = allSupportedTypesFields.sort(sortFields);
|
||||
// optimization before existingFields are synced
|
||||
if (!hasSyncedExistingFields) {
|
||||
return {
|
||||
...defaultFieldGroups,
|
||||
...groupBy(sorted, (field) => {
|
||||
if (field.type === 'document') {
|
||||
return 'specialFields';
|
||||
} else {
|
||||
return 'emptyFields';
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
...defaultFieldGroups,
|
||||
...groupBy(sorted, (field) => {
|
||||
if (field.type === 'document') {
|
||||
return 'specialFields';
|
||||
} else if (containsData(field)) {
|
||||
return 'availableFields';
|
||||
} else return 'emptyFields';
|
||||
}),
|
||||
};
|
||||
}, [allFields, existingFields, currentIndexPattern, hasSyncedExistingFields]);
|
||||
|
||||
if (
|
||||
localState.nameFilter.length &&
|
||||
!field.name.toLowerCase().includes(localState.nameFilter.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const filteredFieldGroups: FieldsGroup = useMemo(() => {
|
||||
const filterFieldGroup = (fieldGroup: IndexPatternField[]) =>
|
||||
fieldGroup.filter((field) => {
|
||||
if (
|
||||
localState.nameFilter.length &&
|
||||
!field.name.toLowerCase().includes(localState.nameFilter.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!showEmptyFields) {
|
||||
const indexField = currentIndexPattern && fieldByName[field.name];
|
||||
const exists =
|
||||
field.type === 'document' ||
|
||||
(indexField && fieldExists(existingFields, currentIndexPattern.title, indexField.name));
|
||||
if (localState.typeFilter.length > 0) {
|
||||
return exists && localState.typeFilter.includes(field.type as DataType);
|
||||
if (localState.typeFilter.length > 0) {
|
||||
return localState.typeFilter.includes(field.type as DataType);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return Object.entries(fieldGroups).reduce((acc, [name, fields]) => {
|
||||
return {
|
||||
...acc,
|
||||
[name]: filterFieldGroup(fields),
|
||||
};
|
||||
}, defaultFieldGroups);
|
||||
}, [fieldGroups, localState.nameFilter, localState.typeFilter]);
|
||||
|
||||
const lazyScroll = useCallback(() => {
|
||||
if (scrollContainer) {
|
||||
const nearBottom =
|
||||
scrollContainer.scrollTop + scrollContainer.clientHeight >
|
||||
scrollContainer.scrollHeight * 0.9;
|
||||
if (nearBottom) {
|
||||
const displayedFieldsLength =
|
||||
(localState.isAvailableAccordionOpen ? filteredFieldGroups.availableFields.length : 0) +
|
||||
(localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0);
|
||||
setPageSize(
|
||||
Math.max(
|
||||
PAGINATION_SIZE,
|
||||
Math.min(pageSize + PAGINATION_SIZE * 0.5, displayedFieldsLength)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
scrollContainer,
|
||||
localState.isAvailableAccordionOpen,
|
||||
localState.isEmptyAccordionOpen,
|
||||
filteredFieldGroups,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
]);
|
||||
|
||||
return exists;
|
||||
const [paginatedAvailableFields, paginatedEmptyFields]: [
|
||||
IndexPatternField[],
|
||||
IndexPatternField[]
|
||||
] = useMemo(() => {
|
||||
const { availableFields, emptyFields } = filteredFieldGroups;
|
||||
const isAvailableAccordionOpen = localState.isAvailableAccordionOpen;
|
||||
const isEmptyAccordionOpen = localState.isEmptyAccordionOpen;
|
||||
|
||||
if (isAvailableAccordionOpen && isEmptyAccordionOpen) {
|
||||
if (availableFields.length > pageSize) {
|
||||
return [availableFields.slice(0, pageSize), []];
|
||||
} else {
|
||||
return [availableFields, emptyFields.slice(0, pageSize - availableFields.length)];
|
||||
}
|
||||
}
|
||||
if (isAvailableAccordionOpen && !isEmptyAccordionOpen) {
|
||||
return [availableFields.slice(0, pageSize), []];
|
||||
}
|
||||
|
||||
if (localState.typeFilter.length > 0) {
|
||||
return localState.typeFilter.includes(field.type as DataType);
|
||||
if (!isAvailableAccordionOpen && isEmptyAccordionOpen) {
|
||||
return [[], emptyFields.slice(0, pageSize)];
|
||||
}
|
||||
return [[], []];
|
||||
}, [
|
||||
localState.isAvailableAccordionOpen,
|
||||
localState.isEmptyAccordionOpen,
|
||||
filteredFieldGroups,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const specialFields = displayedFields.filter((f) => f.type === 'document');
|
||||
const paginatedFields = displayedFields
|
||||
.filter((f) => f.type !== 'document')
|
||||
.sort(sortFields)
|
||||
.slice(0, pageSize);
|
||||
const hilight = localState.nameFilter.toLowerCase();
|
||||
|
||||
const filterByTypeLabel = i18n.translate('xpack.lens.indexPatterns.filterByTypeLabel', {
|
||||
defaultMessage: 'Filter by type',
|
||||
});
|
||||
const fieldProps = useMemo(
|
||||
() => ({
|
||||
core,
|
||||
data,
|
||||
indexPattern: currentIndexPattern,
|
||||
highlight: localState.nameFilter.toLowerCase(),
|
||||
dateRange,
|
||||
query,
|
||||
filters,
|
||||
}),
|
||||
[core, data, currentIndexPattern, dateRange, query, filters, localState.nameFilter]
|
||||
);
|
||||
|
||||
return (
|
||||
<ChildDragDropProvider {...dragDropContext}>
|
||||
|
@ -308,7 +384,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
direction="column"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={null}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div className="lnsInnerIndexPatternDataPanel__header">
|
||||
<ChangeIndexPattern
|
||||
data-test-subj="indexPattern-switcher"
|
||||
|
@ -327,58 +403,59 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
/>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<div className="lnsInnerIndexPatternDataPanel__filterWrapper">
|
||||
<EuiFormControlLayout
|
||||
icon="search"
|
||||
fullWidth
|
||||
clear={{
|
||||
title: i18n.translate('xpack.lens.indexPatterns.clearFiltersLabel', {
|
||||
defaultMessage: 'Clear name and type filters',
|
||||
}),
|
||||
'aria-label': i18n.translate('xpack.lens.indexPatterns.clearFiltersLabel', {
|
||||
defaultMessage: 'Clear name and type filters',
|
||||
}),
|
||||
onClick: () => {
|
||||
trackUiEvent('indexpattern_filters_cleared');
|
||||
clearLocalState();
|
||||
},
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormControlLayout
|
||||
icon="search"
|
||||
fullWidth
|
||||
clear={{
|
||||
title: i18n.translate('xpack.lens.indexPatterns.clearFiltersLabel', {
|
||||
defaultMessage: 'Clear name and type filters',
|
||||
}),
|
||||
'aria-label': i18n.translate('xpack.lens.indexPatterns.clearFiltersLabel', {
|
||||
defaultMessage: 'Clear name and type filters',
|
||||
}),
|
||||
onClick: () => {
|
||||
trackUiEvent('indexpattern_filters_cleared');
|
||||
clearLocalState();
|
||||
},
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="euiFieldText euiFieldText--fullWidth lnsInnerIndexPatternDataPanel__textField"
|
||||
data-test-subj="lnsIndexPatternFieldSearch"
|
||||
placeholder={i18n.translate('xpack.lens.indexPatterns.filterByNameLabel', {
|
||||
defaultMessage: 'Search field names',
|
||||
description: 'Search the list of fields in the index pattern for the provided text',
|
||||
})}
|
||||
value={localState.nameFilter}
|
||||
onChange={(e) => {
|
||||
setLocalState({ ...localState, nameFilter: e.target.value });
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="euiFieldText euiFieldText--fullWidth lnsInnerIndexPatternDataPanel__textField"
|
||||
data-test-subj="lnsIndexPatternFieldSearch"
|
||||
placeholder={i18n.translate('xpack.lens.indexPatterns.filterByNameLabel', {
|
||||
defaultMessage: 'Search field names',
|
||||
description:
|
||||
'Search the list of fields in the index pattern for the provided text',
|
||||
})}
|
||||
value={localState.nameFilter}
|
||||
onChange={(e) => {
|
||||
setLocalState({ ...localState, nameFilter: e.target.value });
|
||||
}}
|
||||
aria-label={i18n.translate('xpack.lens.indexPatterns.filterByNameAriaLabel', {
|
||||
defaultMessage: 'Search fields',
|
||||
})}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
</div>
|
||||
<div className="lnsInnerIndexPatternDataPanel__filtersWrapper">
|
||||
aria-label={i18n.translate('xpack.lens.indexPatterns.filterByNameAriaLabel', {
|
||||
defaultMessage: 'Search fields',
|
||||
})}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
id="dataPanelTypeFilter"
|
||||
panelClassName="euiFilterGroup__popoverPanel"
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="rightDown"
|
||||
anchorPosition="rightUp"
|
||||
display="block"
|
||||
isOpen={localState.isTypeFilterOpen}
|
||||
closePopover={() => setLocalState(() => ({ ...localState, isTypeFilterOpen: false }))}
|
||||
button={
|
||||
<EuiFacetButton
|
||||
<EuiFilterButton
|
||||
iconType="arrowDown"
|
||||
isSelected={localState.isTypeFilterOpen}
|
||||
numFilters={localState.typeFilter.length}
|
||||
hasActiveFilters={!!localState.typeFilter.length}
|
||||
numActiveFilters={localState.typeFilter.length}
|
||||
data-test-subj="lnsIndexPatternFiltersToggle"
|
||||
className="lnsInnerIndexPatternDataPanel__filterButton"
|
||||
quantity={localState.typeFilter.length}
|
||||
icon={<EuiIcon type="filter" />}
|
||||
isSelected={localState.typeFilter.length ? true : false}
|
||||
onClick={() => {
|
||||
setLocalState((s) => ({
|
||||
...s,
|
||||
|
@ -386,11 +463,10 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
}));
|
||||
}}
|
||||
>
|
||||
{filterByTypeLabel}
|
||||
</EuiFacetButton>
|
||||
{fieldFiltersLabel}
|
||||
</EuiFilterButton>
|
||||
}
|
||||
>
|
||||
<EuiPopoverTitle>{filterByTypeLabel}</EuiPopoverTitle>
|
||||
<FixedEuiContextMenuPanel
|
||||
watchedItemProps={['icon', 'disabled']}
|
||||
data-test-subj="lnsIndexPatternTypeFilterOptions"
|
||||
|
@ -416,22 +492,10 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
</EuiContextMenuItem>
|
||||
))}
|
||||
/>
|
||||
<EuiPopoverFooter>
|
||||
<EuiSwitch
|
||||
compressed
|
||||
checked={!showEmptyFields}
|
||||
onChange={() => {
|
||||
trackUiEvent('indexpattern_existence_toggled');
|
||||
onToggleEmptyFields();
|
||||
}}
|
||||
label={i18n.translate('xpack.lens.indexPatterns.toggleEmptyFieldsSwitch', {
|
||||
defaultMessage: 'Only show fields with data',
|
||||
})}
|
||||
data-test-subj="lnsEmptyFilter"
|
||||
/>
|
||||
</EuiPopoverFooter>
|
||||
</EuiPopover>
|
||||
</div>
|
||||
</EuiFilterGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<div
|
||||
className="lnsInnerIndexPatternDataPanel__listWrapper"
|
||||
ref={(el) => {
|
||||
|
@ -440,101 +504,95 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
setScrollContainer(el);
|
||||
}
|
||||
}}
|
||||
onScroll={lazyScroll}
|
||||
onScroll={throttle(lazyScroll, 100)}
|
||||
>
|
||||
<div className="lnsInnerIndexPatternDataPanel__list">
|
||||
{specialFields.map((field) => (
|
||||
{filteredFieldGroups.specialFields.map((field: IndexPatternField) => (
|
||||
<FieldItem
|
||||
core={core}
|
||||
data={data}
|
||||
key={field.name}
|
||||
indexPattern={currentIndexPattern}
|
||||
{...fieldProps}
|
||||
exists={!!fieldGroups.availableFields.length}
|
||||
field={field}
|
||||
highlight={hilight}
|
||||
exists={paginatedFields.length > 0}
|
||||
dateRange={dateRange}
|
||||
query={query}
|
||||
filters={filters}
|
||||
hideDetails={true}
|
||||
key={field.name}
|
||||
/>
|
||||
))}
|
||||
{specialFields.length > 0 && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormLabel>
|
||||
{i18n.translate('xpack.lens.indexPattern.individualFieldsLabel', {
|
||||
defaultMessage: 'Individual fields',
|
||||
})}
|
||||
</EuiFormLabel>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
{paginatedFields.map((field) => {
|
||||
const overallField = fieldByName[field.name];
|
||||
return (
|
||||
<FieldItem
|
||||
core={core}
|
||||
data={data}
|
||||
indexPattern={currentIndexPattern}
|
||||
key={field.name}
|
||||
field={field}
|
||||
highlight={hilight}
|
||||
exists={
|
||||
overallField &&
|
||||
fieldExists(existingFields, currentIndexPattern.title, overallField.name)
|
||||
}
|
||||
dateRange={dateRange}
|
||||
query={query}
|
||||
filters={filters}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{paginatedFields.length === 0 && (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={
|
||||
localState.typeFilter.length || localState.nameFilter.length
|
||||
? i18n.translate('xpack.lens.indexPatterns.noFilteredFieldsLabel', {
|
||||
defaultMessage: 'No fields match the current filters.',
|
||||
})
|
||||
: showEmptyFields
|
||||
? i18n.translate('xpack.lens.indexPatterns.noFieldsLabel', {
|
||||
defaultMessage: 'No fields exist in this index pattern.',
|
||||
})
|
||||
: i18n.translate('xpack.lens.indexPatterns.emptyFieldsWithDataLabel', {
|
||||
defaultMessage: 'Looks like you don’t have any data.',
|
||||
})
|
||||
}
|
||||
>
|
||||
{(!showEmptyFields ||
|
||||
localState.typeFilter.length ||
|
||||
localState.nameFilter.length) && (
|
||||
<>
|
||||
<strong>
|
||||
{i18n.translate('xpack.lens.indexPatterns.noFields.tryText', {
|
||||
defaultMessage: 'Try:',
|
||||
})}
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
{i18n.translate('xpack.lens.indexPatterns.noFields.extendTimeBullet', {
|
||||
defaultMessage: 'Extending the time range',
|
||||
})}
|
||||
</li>
|
||||
<li>
|
||||
{i18n.translate('xpack.lens.indexPatterns.noFields.fieldFilterBullet', {
|
||||
defaultMessage:
|
||||
'Using {filterByTypeLabel} {arrow} to show fields without data',
|
||||
values: { filterByTypeLabel, arrow: '↑' },
|
||||
})}
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
)}
|
||||
<EuiSpacer size="s" />
|
||||
<FieldsAccordion
|
||||
initialIsOpen={localState.isAvailableAccordionOpen}
|
||||
id="lnsIndexPatternAvailableFields"
|
||||
label={i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', {
|
||||
defaultMessage: 'Available fields',
|
||||
})}
|
||||
exists={true}
|
||||
hasLoaded={!!hasSyncedExistingFields}
|
||||
fieldsCount={filteredFieldGroups.availableFields.length}
|
||||
isFiltered={
|
||||
filteredFieldGroups.availableFields.length !== fieldGroups.availableFields.length
|
||||
}
|
||||
paginatedFields={paginatedAvailableFields}
|
||||
fieldProps={fieldProps}
|
||||
onToggle={(open) => {
|
||||
setLocalState((s) => ({
|
||||
...s,
|
||||
isAvailableAccordionOpen: open,
|
||||
}));
|
||||
const displayedFieldLength =
|
||||
(open ? filteredFieldGroups.availableFields.length : 0) +
|
||||
(localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0);
|
||||
setPageSize(
|
||||
Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength))
|
||||
);
|
||||
}}
|
||||
renderCallout={
|
||||
<NoFieldsCallout
|
||||
isAffectedByGlobalFilter={!!filters.length}
|
||||
isAffectedByFieldFilter={
|
||||
!!(localState.typeFilter.length || localState.nameFilter.length)
|
||||
}
|
||||
isAffectedByTimerange={true}
|
||||
existFieldsInIndex={!!fieldGroups.emptyFields.length}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<FieldsAccordion
|
||||
initialIsOpen={localState.isEmptyAccordionOpen}
|
||||
isFiltered={
|
||||
filteredFieldGroups.emptyFields.length !== fieldGroups.emptyFields.length
|
||||
}
|
||||
fieldsCount={filteredFieldGroups.emptyFields.length}
|
||||
paginatedFields={paginatedEmptyFields}
|
||||
hasLoaded={!!hasSyncedExistingFields}
|
||||
exists={false}
|
||||
fieldProps={fieldProps}
|
||||
id="lnsIndexPatternEmptyFields"
|
||||
label={i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', {
|
||||
defaultMessage: 'Empty fields',
|
||||
})}
|
||||
onToggle={(open) => {
|
||||
setLocalState((s) => ({
|
||||
...s,
|
||||
isEmptyAccordionOpen: open,
|
||||
}));
|
||||
const displayedFieldLength =
|
||||
(localState.isAvailableAccordionOpen
|
||||
? filteredFieldGroups.availableFields.length
|
||||
: 0) + (open ? filteredFieldGroups.emptyFields.length : 0);
|
||||
setPageSize(
|
||||
Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength))
|
||||
);
|
||||
}}
|
||||
renderCallout={
|
||||
<NoFieldsCallout
|
||||
isAffectedByFieldFilter={
|
||||
!!(localState.typeFilter.length || localState.nameFilter.length)
|
||||
}
|
||||
existFieldsInIndex={!!fieldGroups.emptyFields.length}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</div>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -79,7 +79,6 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
indexPatternRefs: [],
|
||||
indexPatterns: expectedIndexPatterns,
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
existingFields: {
|
||||
'my-fake-index-pattern': {
|
||||
timestamp: true,
|
||||
|
@ -1258,7 +1257,6 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
},
|
||||
},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
myLayer: {
|
||||
indexPatternId: 'foo',
|
||||
|
|
|
@ -27,7 +27,6 @@ export interface FieldChoice {
|
|||
|
||||
export interface FieldSelectProps {
|
||||
currentIndexPattern: IndexPattern;
|
||||
showEmptyFields: boolean;
|
||||
fieldMap: Record<string, IndexPatternField>;
|
||||
incompatibleSelectedOperationType: OperationType | null;
|
||||
selectedColumnOperationType?: OperationType;
|
||||
|
@ -40,7 +39,6 @@ export interface FieldSelectProps {
|
|||
|
||||
export function FieldSelect({
|
||||
currentIndexPattern,
|
||||
showEmptyFields,
|
||||
fieldMap,
|
||||
incompatibleSelectedOperationType,
|
||||
selectedColumnOperationType,
|
||||
|
@ -69,6 +67,10 @@ export function FieldSelect({
|
|||
(field) => fieldMap[field].type === 'document'
|
||||
);
|
||||
|
||||
const containsData = (field: string) =>
|
||||
fieldMap[field].type === 'document' ||
|
||||
fieldExists(existingFields, currentIndexPattern.title, field);
|
||||
|
||||
function fieldNamesToOptions(items: string[]) {
|
||||
return items
|
||||
.map((field) => ({
|
||||
|
@ -82,12 +84,9 @@ export function FieldSelect({
|
|||
? selectedColumnOperationType
|
||||
: undefined,
|
||||
},
|
||||
exists:
|
||||
fieldMap[field].type === 'document' ||
|
||||
fieldExists(existingFields, currentIndexPattern.title, field),
|
||||
exists: containsData(field),
|
||||
compatible: isCompatibleWithCurrentOperation(field),
|
||||
}))
|
||||
.filter((field) => showEmptyFields || field.exists)
|
||||
.sort((a, b) => {
|
||||
if (a.compatible && !b.compatible) {
|
||||
return -1;
|
||||
|
@ -108,18 +107,33 @@ export function FieldSelect({
|
|||
}));
|
||||
}
|
||||
|
||||
const fieldOptions: unknown[] = fieldNamesToOptions(specialFields);
|
||||
const [availableFields, emptyFields] = _.partition(normalFields, containsData);
|
||||
|
||||
if (fields.length > 0) {
|
||||
fieldOptions.push({
|
||||
label: i18n.translate('xpack.lens.indexPattern.individualFieldsLabel', {
|
||||
defaultMessage: 'Individual fields',
|
||||
}),
|
||||
options: fieldNamesToOptions(normalFields),
|
||||
});
|
||||
}
|
||||
const constructFieldsOptions = (fieldsArr: string[], label: string) =>
|
||||
fieldsArr.length > 0 && {
|
||||
label,
|
||||
options: fieldNamesToOptions(fieldsArr),
|
||||
};
|
||||
|
||||
return fieldOptions;
|
||||
const availableFieldsOptions = constructFieldsOptions(
|
||||
availableFields,
|
||||
i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', {
|
||||
defaultMessage: 'Available fields',
|
||||
})
|
||||
);
|
||||
|
||||
const emptyFieldsOptions = constructFieldsOptions(
|
||||
emptyFields,
|
||||
i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', {
|
||||
defaultMessage: 'Empty fields',
|
||||
})
|
||||
);
|
||||
|
||||
return [
|
||||
...fieldNamesToOptions(specialFields),
|
||||
availableFieldsOptions,
|
||||
emptyFieldsOptions,
|
||||
].filter(Boolean);
|
||||
}, [
|
||||
incompatibleSelectedOperationType,
|
||||
selectedColumnOperationType,
|
||||
|
@ -127,7 +141,6 @@ export function FieldSelect({
|
|||
operationFieldSupportMatrix,
|
||||
currentIndexPattern,
|
||||
fieldMap,
|
||||
showEmptyFields,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -200,7 +200,6 @@ export function PopoverEditor(props: PopoverEditorProps) {
|
|||
<FieldSelect
|
||||
currentIndexPattern={currentIndexPattern}
|
||||
existingFields={state.existingFields}
|
||||
showEmptyFields={state.showEmptyFields}
|
||||
fieldMap={fieldMap}
|
||||
operationFieldSupportMatrix={operationFieldSupportMatrix}
|
||||
selectedColumnOperationType={selectedColumn && selectedColumn.operationType}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { EuiLoadingSpinner, EuiPopover } from '@elastic/eui';
|
||||
import { FieldItem, FieldItemProps } from './field_item';
|
||||
import { InnerFieldItem, FieldItemProps } from './field_item';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
|
||||
|
@ -94,7 +94,7 @@ describe('IndexPattern Field Item', () => {
|
|||
core.http.post.mockImplementationOnce(() => {
|
||||
return Promise.resolve({});
|
||||
});
|
||||
const wrapper = mountWithIntl(<FieldItem {...defaultProps} />);
|
||||
const wrapper = mountWithIntl(<InnerFieldItem {...defaultProps} />);
|
||||
|
||||
await act(async () => {
|
||||
wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
|
||||
|
@ -119,7 +119,7 @@ describe('IndexPattern Field Item', () => {
|
|||
});
|
||||
});
|
||||
|
||||
const wrapper = mountWithIntl(<FieldItem {...defaultProps} />);
|
||||
const wrapper = mountWithIntl(<InnerFieldItem {...defaultProps} />);
|
||||
|
||||
wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
|
||||
|
||||
|
|
|
@ -49,6 +49,8 @@ import { IndexPattern, IndexPatternField } from './types';
|
|||
import { LensFieldIcon } from './lens_field_icon';
|
||||
import { trackUiEvent } from '../lens_ui_telemetry';
|
||||
|
||||
import { debouncedComponent } from '../debounced_component';
|
||||
|
||||
export interface FieldItemProps {
|
||||
core: DatasourceDataPanelProps['core'];
|
||||
data: DataPublicPluginStart;
|
||||
|
@ -78,7 +80,7 @@ function wrapOnDot(str?: string) {
|
|||
return str ? str.replace(/\./g, '.\u200B') : '';
|
||||
}
|
||||
|
||||
export const FieldItem = React.memo(function FieldItem(props: FieldItemProps) {
|
||||
export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) {
|
||||
const {
|
||||
core,
|
||||
field,
|
||||
|
@ -239,7 +241,9 @@ export const FieldItem = React.memo(function FieldItem(props: FieldItemProps) {
|
|||
<FieldItemPopoverContents {...state} {...props} />
|
||||
</EuiPopover>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const FieldItem = debouncedComponent(InnerFieldItem);
|
||||
|
||||
function FieldItemPopoverContents(props: State & FieldItemProps) {
|
||||
const {
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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 { EuiLoadingSpinner, EuiNotificationBadge } from '@elastic/eui';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
|
||||
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
|
||||
import { IndexPattern } from './types';
|
||||
import { FieldItem } from './field_item';
|
||||
import { FieldsAccordion, FieldsAccordionProps, FieldItemSharedProps } from './fields_accordion';
|
||||
|
||||
describe('Fields Accordion', () => {
|
||||
let defaultProps: FieldsAccordionProps;
|
||||
let indexPattern: IndexPattern;
|
||||
let core: ReturnType<typeof coreMock['createSetup']>;
|
||||
let data: DataPublicPluginStart;
|
||||
let fieldProps: FieldItemSharedProps;
|
||||
|
||||
beforeEach(() => {
|
||||
indexPattern = {
|
||||
id: '1',
|
||||
title: 'my-fake-index-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
} as IndexPattern;
|
||||
core = coreMock.createSetup();
|
||||
data = dataPluginMock.createStartContract();
|
||||
core.http.post.mockClear();
|
||||
|
||||
fieldProps = {
|
||||
indexPattern,
|
||||
data,
|
||||
core,
|
||||
highlight: '',
|
||||
dateRange: {
|
||||
fromDate: 'now-7d',
|
||||
toDate: 'now',
|
||||
},
|
||||
query: { query: '', language: 'lucene' },
|
||||
filters: [],
|
||||
};
|
||||
|
||||
defaultProps = {
|
||||
initialIsOpen: true,
|
||||
onToggle: jest.fn(),
|
||||
id: 'id',
|
||||
label: 'label',
|
||||
hasLoaded: true,
|
||||
fieldsCount: 2,
|
||||
isFiltered: false,
|
||||
paginatedFields: indexPattern.fields,
|
||||
fieldProps,
|
||||
renderCallout: <div id="lens-test-callout">Callout</div>,
|
||||
exists: true,
|
||||
};
|
||||
});
|
||||
|
||||
it('renders correct number of Field Items', () => {
|
||||
const wrapper = mountWithIntl(<FieldsAccordion {...defaultProps} />);
|
||||
expect(wrapper.find(FieldItem).length).toEqual(2);
|
||||
});
|
||||
|
||||
it('renders callout if no fields', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<FieldsAccordion {...defaultProps} fieldsCount={0} paginatedFields={[]} />
|
||||
);
|
||||
expect(wrapper.find('#lens-test-callout').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('renders accented notificationBadge state if isFiltered', () => {
|
||||
const wrapper = mountWithIntl(<FieldsAccordion {...defaultProps} isFiltered={true} />);
|
||||
expect(wrapper.find(EuiNotificationBadge).prop('color')).toEqual('accent');
|
||||
});
|
||||
|
||||
it('renders spinner if has not loaded', () => {
|
||||
const wrapper = mountWithIntl(<FieldsAccordion {...defaultProps} hasLoaded={false} />);
|
||||
expect(wrapper.find(EuiLoadingSpinner).length).toEqual(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 './datapanel.scss';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import {
|
||||
EuiText,
|
||||
EuiNotificationBadge,
|
||||
EuiSpacer,
|
||||
EuiAccordion,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import { DataPublicPluginStart } from 'src/plugins/data/public';
|
||||
import { IndexPatternField } from './types';
|
||||
import { FieldItem } from './field_item';
|
||||
import { Query, Filter } from '../../../../../src/plugins/data/public';
|
||||
import { DatasourceDataPanelProps } from '../types';
|
||||
import { IndexPattern } from './types';
|
||||
|
||||
export interface FieldItemSharedProps {
|
||||
core: DatasourceDataPanelProps['core'];
|
||||
data: DataPublicPluginStart;
|
||||
indexPattern: IndexPattern;
|
||||
highlight?: string;
|
||||
query: Query;
|
||||
dateRange: DatasourceDataPanelProps['dateRange'];
|
||||
filters: Filter[];
|
||||
}
|
||||
|
||||
export interface FieldsAccordionProps {
|
||||
initialIsOpen: boolean;
|
||||
onToggle: (open: boolean) => void;
|
||||
id: string;
|
||||
label: string;
|
||||
hasLoaded: boolean;
|
||||
fieldsCount: number;
|
||||
isFiltered: boolean;
|
||||
paginatedFields: IndexPatternField[];
|
||||
fieldProps: FieldItemSharedProps;
|
||||
renderCallout: JSX.Element;
|
||||
exists: boolean;
|
||||
}
|
||||
|
||||
export const InnerFieldsAccordion = function InnerFieldsAccordion({
|
||||
initialIsOpen,
|
||||
onToggle,
|
||||
id,
|
||||
label,
|
||||
hasLoaded,
|
||||
fieldsCount,
|
||||
isFiltered,
|
||||
paginatedFields,
|
||||
fieldProps,
|
||||
renderCallout,
|
||||
exists,
|
||||
}: FieldsAccordionProps) {
|
||||
const renderField = useCallback(
|
||||
(field: IndexPatternField) => {
|
||||
return <FieldItem {...fieldProps} key={field.name} field={field} exists={!!exists} />;
|
||||
},
|
||||
[fieldProps, exists]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
initialIsOpen={initialIsOpen}
|
||||
onToggle={onToggle}
|
||||
data-test-subj={id}
|
||||
id={id}
|
||||
buttonContent={
|
||||
<EuiText size="xs">
|
||||
<strong>{label}</strong>
|
||||
</EuiText>
|
||||
}
|
||||
extraAction={
|
||||
hasLoaded ? (
|
||||
<EuiNotificationBadge size="m" color={isFiltered ? 'accent' : 'subdued'}>
|
||||
{fieldsCount}
|
||||
</EuiNotificationBadge>
|
||||
) : (
|
||||
<EuiLoadingSpinner size="m" />
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
{hasLoaded &&
|
||||
(!!fieldsCount ? (
|
||||
<div className="lnsInnerIndexPatternDataPanel__fieldItems">
|
||||
{paginatedFields && paginatedFields.map(renderField)}
|
||||
</div>
|
||||
) : (
|
||||
renderCallout
|
||||
))}
|
||||
</EuiAccordion>
|
||||
);
|
||||
};
|
||||
|
||||
export const FieldsAccordion = memo(InnerFieldsAccordion);
|
|
@ -127,7 +127,6 @@ function stateFromPersistedState(
|
|||
indexPatterns: expectedIndexPatterns,
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
showEmptyFields: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -402,7 +401,6 @@ describe('IndexPattern Data Source', () => {
|
|||
},
|
||||
},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
};
|
||||
expect(indexPatternDatasource.insertLayer(state, 'newLayer')).toEqual({
|
||||
...state,
|
||||
|
@ -423,7 +421,6 @@ describe('IndexPattern Data Source', () => {
|
|||
const state = {
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
showEmptyFields: false,
|
||||
indexPatterns: expectedIndexPatterns,
|
||||
layers: {
|
||||
first: {
|
||||
|
@ -458,7 +455,6 @@ describe('IndexPattern Data Source', () => {
|
|||
indexPatternDatasource.getLayers({
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
showEmptyFields: false,
|
||||
indexPatterns: expectedIndexPatterns,
|
||||
layers: {
|
||||
first: {
|
||||
|
@ -484,7 +480,6 @@ describe('IndexPattern Data Source', () => {
|
|||
indexPatternDatasource.getMetaData({
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
showEmptyFields: false,
|
||||
indexPatterns: expectedIndexPatterns,
|
||||
layers: {
|
||||
first: {
|
||||
|
|
|
@ -146,7 +146,6 @@ function testInitialState(): IndexPatternPrivateState {
|
|||
},
|
||||
},
|
||||
},
|
||||
showEmptyFields: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -305,7 +304,6 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
indexPatterns: {
|
||||
1: {
|
||||
id: '1',
|
||||
|
@ -510,7 +508,6 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
indexPatterns: {
|
||||
1: {
|
||||
id: '1',
|
||||
|
@ -1049,7 +1046,6 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
it('returns no suggestions if there are no columns', () => {
|
||||
expect(
|
||||
getDatasourceSuggestionsFromCurrentState({
|
||||
showEmptyFields: false,
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
indexPatterns: expectedIndexPatterns,
|
||||
|
@ -1355,7 +1351,6 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
],
|
||||
},
|
||||
},
|
||||
showEmptyFields: true,
|
||||
layers: {
|
||||
first: {
|
||||
...initialState.layers.first,
|
||||
|
@ -1475,7 +1470,6 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
],
|
||||
},
|
||||
},
|
||||
showEmptyFields: true,
|
||||
layers: {
|
||||
first: {
|
||||
...initialState.layers.first,
|
||||
|
@ -1529,7 +1523,6 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
],
|
||||
},
|
||||
},
|
||||
showEmptyFields: true,
|
||||
layers: {
|
||||
first: {
|
||||
...initialState.layers.first,
|
||||
|
@ -1560,7 +1553,6 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
indexPatterns: expectedIndexPatterns,
|
||||
showEmptyFields: true,
|
||||
layers: {
|
||||
first: {
|
||||
...initialState.layers.first,
|
||||
|
|
|
@ -22,7 +22,6 @@ const initialState: IndexPatternPrivateState = {
|
|||
],
|
||||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
|
|
@ -294,7 +294,6 @@ describe('loader', () => {
|
|||
a: sampleIndexPatterns.a,
|
||||
},
|
||||
layers: {},
|
||||
showEmptyFields: false,
|
||||
});
|
||||
expect(storage.set).toHaveBeenCalledWith('lens-settings', {
|
||||
indexPatternId: 'a',
|
||||
|
@ -361,7 +360,6 @@ describe('loader', () => {
|
|||
b: sampleIndexPatterns.b,
|
||||
},
|
||||
layers: {},
|
||||
showEmptyFields: false,
|
||||
});
|
||||
expect(storage.set).toHaveBeenCalledWith('lens-settings', {
|
||||
indexPatternId: 'b',
|
||||
|
@ -414,7 +412,6 @@ describe('loader', () => {
|
|||
b: sampleIndexPatterns.b,
|
||||
},
|
||||
layers: savedState.layers,
|
||||
showEmptyFields: false,
|
||||
});
|
||||
|
||||
expect(storage.set).toHaveBeenCalledWith('lens-settings', {
|
||||
|
@ -432,7 +429,6 @@ describe('loader', () => {
|
|||
indexPatterns: {},
|
||||
existingFields: {},
|
||||
layers: {},
|
||||
showEmptyFields: true,
|
||||
};
|
||||
const storage = createMockStorage({ indexPatternId: 'b' });
|
||||
|
||||
|
@ -467,7 +463,6 @@ describe('loader', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
layers: {},
|
||||
showEmptyFields: true,
|
||||
};
|
||||
|
||||
const storage = createMockStorage({ indexPatternId: 'b' });
|
||||
|
@ -525,7 +520,6 @@ describe('loader', () => {
|
|||
indexPatternId: 'a',
|
||||
},
|
||||
},
|
||||
showEmptyFields: true,
|
||||
};
|
||||
|
||||
const storage = createMockStorage({ indexPatternId: 'a' });
|
||||
|
@ -594,7 +588,6 @@ describe('loader', () => {
|
|||
indexPatternId: 'a',
|
||||
},
|
||||
},
|
||||
showEmptyFields: true,
|
||||
};
|
||||
|
||||
const storage = createMockStorage({ indexPatternId: 'b' });
|
||||
|
|
|
@ -118,7 +118,6 @@ export async function loadInitialState({
|
|||
currentIndexPatternId,
|
||||
indexPatternRefs,
|
||||
indexPatterns,
|
||||
showEmptyFields: false,
|
||||
existingFields: {},
|
||||
};
|
||||
}
|
||||
|
@ -128,7 +127,6 @@ export async function loadInitialState({
|
|||
indexPatternRefs,
|
||||
indexPatterns,
|
||||
layers: {},
|
||||
showEmptyFields: false,
|
||||
existingFields: {},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { NoFieldsCallout } from './no_fields_callout';
|
||||
|
||||
describe('NoFieldCallout', () => {
|
||||
it('renders properly for index with no fields', () => {
|
||||
const component = shallow(
|
||||
<NoFieldsCallout existFieldsInIndex={false} isAffectedByFieldFilter={false} />
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders properly when affected by field filters, global filter and timerange', () => {
|
||||
const component = shallow(
|
||||
<NoFieldsCallout
|
||||
existFieldsInIndex={true}
|
||||
isAffectedByFieldFilter={true}
|
||||
isAffectedByTimerange={true}
|
||||
isAffectedByGlobalFilter={true}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders properly when affected by field filter', () => {
|
||||
const component = shallow(
|
||||
<NoFieldsCallout existFieldsInIndex={true} isAffectedByFieldFilter={true} />
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { EuiCallOut } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const NoFieldsCallout = ({
|
||||
isAffectedByFieldFilter,
|
||||
existFieldsInIndex,
|
||||
isAffectedByTimerange = false,
|
||||
isAffectedByGlobalFilter = false,
|
||||
}: {
|
||||
isAffectedByFieldFilter: boolean;
|
||||
existFieldsInIndex: boolean;
|
||||
isAffectedByTimerange?: boolean;
|
||||
isAffectedByGlobalFilter?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={
|
||||
isAffectedByFieldFilter
|
||||
? i18n.translate('xpack.lens.indexPatterns.noFilteredFieldsLabel', {
|
||||
defaultMessage: 'No fields match the selected filters.',
|
||||
})
|
||||
: existFieldsInIndex
|
||||
? i18n.translate('xpack.lens.indexPatterns.noDataLabel', {
|
||||
defaultMessage: `There are no available fields that contain data.`,
|
||||
})
|
||||
: i18n.translate('xpack.lens.indexPatterns.noFieldsLabel', {
|
||||
defaultMessage: 'No fields exist in this index pattern.',
|
||||
})
|
||||
}
|
||||
>
|
||||
{existFieldsInIndex && (
|
||||
<>
|
||||
<strong>
|
||||
{i18n.translate('xpack.lens.indexPatterns.noFields.tryText', {
|
||||
defaultMessage: 'Try:',
|
||||
})}
|
||||
</strong>
|
||||
<ul>
|
||||
{isAffectedByTimerange && (
|
||||
<>
|
||||
<li>
|
||||
{i18n.translate('xpack.lens.indexPatterns.noFields.extendTimeBullet', {
|
||||
defaultMessage: 'Extending the time range',
|
||||
})}
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
{isAffectedByFieldFilter ? (
|
||||
<li>
|
||||
{i18n.translate('xpack.lens.indexPatterns.noFields.fieldTypeFilterBullet', {
|
||||
defaultMessage: 'Using different field filters',
|
||||
})}
|
||||
</li>
|
||||
) : null}
|
||||
{isAffectedByGlobalFilter ? (
|
||||
<li>
|
||||
{i18n.translate('xpack.lens.indexPatterns.noFields.globalFiltersBullet', {
|
||||
defaultMessage: 'Changing the global filters',
|
||||
})}
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -51,7 +51,6 @@ describe('date_histogram', () => {
|
|||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
indexPatterns: {
|
||||
1: {
|
||||
id: '1',
|
||||
|
|
|
@ -34,7 +34,6 @@ describe('terms', () => {
|
|||
indexPatterns: {},
|
||||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
|
|
@ -147,7 +147,6 @@ describe('getOperationTypesForField', () => {
|
|||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
indexPatterns: expectedIndexPatterns,
|
||||
layers: {
|
||||
first: {
|
||||
|
|
|
@ -42,7 +42,6 @@ describe('state_helpers', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
@ -96,7 +95,6 @@ describe('state_helpers', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
@ -147,7 +145,6 @@ describe('state_helpers', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
@ -188,7 +185,6 @@ describe('state_helpers', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
@ -222,7 +218,6 @@ describe('state_helpers', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
@ -284,7 +279,6 @@ describe('state_helpers', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
@ -337,7 +331,6 @@ describe('state_helpers', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
@ -417,7 +410,6 @@ describe('state_helpers', () => {
|
|||
existingFields: {},
|
||||
indexPatterns: {},
|
||||
currentIndexPatternId: '1',
|
||||
showEmptyFields: false,
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
|
|
|
@ -51,7 +51,6 @@ export type IndexPatternPrivateState = IndexPatternPersistedState & {
|
|||
* indexPatternId -> fieldName -> boolean
|
||||
*/
|
||||
existingFields: Record<string, Record<string, boolean>>;
|
||||
showEmptyFields: boolean;
|
||||
};
|
||||
|
||||
export interface IndexPatternRef {
|
||||
|
|
|
@ -8651,7 +8651,6 @@
|
|||
"xpack.lens.indexPattern.groupingSecondDateHistogram": "各 {target} の日付",
|
||||
"xpack.lens.indexPattern.groupingSecondTerms": "各 {target} のトップの値",
|
||||
"xpack.lens.indexPattern.indexPatternLoadError": "インデックスパターンの読み込み中にエラーが発生",
|
||||
"xpack.lens.indexPattern.individualFieldsLabel": "個々のフィールド",
|
||||
"xpack.lens.indexPattern.invalidInterval": "無効な間隔値",
|
||||
"xpack.lens.indexPattern.invalidOperationLabel": "この関数を使用するには、別のフィールドを選択してください。",
|
||||
"xpack.lens.indexPattern.max": "最高",
|
||||
|
@ -8682,16 +8681,11 @@
|
|||
"xpack.lens.indexPattern.termsOf": "{name} のトップの値",
|
||||
"xpack.lens.indexPattern.uniqueLabel": "{label} [{num}]",
|
||||
"xpack.lens.indexPatterns.clearFiltersLabel": "名前とタイプフィルターを消去",
|
||||
"xpack.lens.indexPatterns.emptyFieldsWithDataLabel": "データがないようです。",
|
||||
"xpack.lens.indexPatterns.filterByNameAriaLabel": "検索フィールド",
|
||||
"xpack.lens.indexPatterns.filterByNameLabel": "フィールドを検索",
|
||||
"xpack.lens.indexPatterns.filterByTypeLabel": "タイプでフィルタリング",
|
||||
"xpack.lens.indexPatterns.noFields.extendTimeBullet": "時間範囲を拡張中",
|
||||
"xpack.lens.indexPatterns.noFields.fieldFilterBullet": "{filterByTypeLabel} {arrow} を使用してデータなしのフィールドを表示",
|
||||
"xpack.lens.indexPatterns.noFields.tryText": "試行対象:",
|
||||
"xpack.lens.indexPatterns.noFieldsLabel": "このインデックスパターンにはフィールドがありません。",
|
||||
"xpack.lens.indexPatterns.noFilteredFieldsLabel": "現在のフィルターと一致するフィールドはありません。",
|
||||
"xpack.lens.indexPatterns.toggleEmptyFieldsSwitch": "データがあるフィールドだけを表示",
|
||||
"xpack.lens.indexPatternSuggestion.removeLayerLabel": "{indexPatternTitle}のみを表示",
|
||||
"xpack.lens.indexPatternSuggestion.removeLayerPositionLabel": "レイヤー{layerNumber}のみを表示",
|
||||
"xpack.lens.lensSavedObjectLabel": "レンズビジュアライゼーション",
|
||||
|
|
|
@ -8655,7 +8655,6 @@
|
|||
"xpack.lens.indexPattern.groupingSecondDateHistogram": "每个 {target} 的日期",
|
||||
"xpack.lens.indexPattern.groupingSecondTerms": "每个 {target} 的排名最前值",
|
||||
"xpack.lens.indexPattern.indexPatternLoadError": "加载索引模式时出错",
|
||||
"xpack.lens.indexPattern.individualFieldsLabel": "各个字段",
|
||||
"xpack.lens.indexPattern.invalidInterval": "时间间隔值无效",
|
||||
"xpack.lens.indexPattern.invalidOperationLabel": "要使用此函数,请选择不同的字段。",
|
||||
"xpack.lens.indexPattern.max": "最大值",
|
||||
|
@ -8686,16 +8685,11 @@
|
|||
"xpack.lens.indexPattern.termsOf": "{name} 的排名最前值",
|
||||
"xpack.lens.indexPattern.uniqueLabel": "{label} [{num}]",
|
||||
"xpack.lens.indexPatterns.clearFiltersLabel": "清除名称和类型筛选",
|
||||
"xpack.lens.indexPatterns.emptyFieldsWithDataLabel": "似乎您没有任何数据。",
|
||||
"xpack.lens.indexPatterns.filterByNameAriaLabel": "搜索字段",
|
||||
"xpack.lens.indexPatterns.filterByNameLabel": "搜索字段",
|
||||
"xpack.lens.indexPatterns.filterByTypeLabel": "按类型筛选",
|
||||
"xpack.lens.indexPatterns.noFields.extendTimeBullet": "延伸时间范围",
|
||||
"xpack.lens.indexPatterns.noFields.fieldFilterBullet": "使用 {filterByTypeLabel} {arrow} 显示没有数据的字段",
|
||||
"xpack.lens.indexPatterns.noFields.tryText": "尝试:",
|
||||
"xpack.lens.indexPatterns.noFieldsLabel": "在此索引模式中不存在任何字段。",
|
||||
"xpack.lens.indexPatterns.noFilteredFieldsLabel": "没有任何字段匹配当前筛选。",
|
||||
"xpack.lens.indexPatterns.toggleEmptyFieldsSwitch": "仅显示具有数据的字段",
|
||||
"xpack.lens.indexPatternSuggestion.removeLayerLabel": "仅显示 {indexPatternTitle}",
|
||||
"xpack.lens.indexPatternSuggestion.removeLayerPositionLabel": "仅显示图层 {layerNumber}",
|
||||
"xpack.lens.lensSavedObjectLabel": "Lens 可视化",
|
||||
|
|
|
@ -30,15 +30,6 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
|
|||
await testSubjects.click('lnsIndexPatternFiltersToggle');
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the field existence checkbox.
|
||||
*/
|
||||
async toggleExistenceFilter() {
|
||||
await this.toggleIndexPatternFiltersPopover();
|
||||
await testSubjects.click('lnsEmptyFilter');
|
||||
await this.toggleIndexPatternFiltersPopover();
|
||||
},
|
||||
|
||||
async findAllFields() {
|
||||
return await testSubjects.findAll('lnsFieldListPanelField');
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue