Move react controls into controls plugin (#190166)

Move react_controls from examples plugin to controls plugin. This PR
does not effect user facing features since it does not effect the legacy
control group implementation or its usage. Future PRs will integrate the
new control group with dashboard and ControlGroupRenderer and then
remove the legacy control group implementation.

PR increases page load bundle size because of new action registration
and the control group react embeddable registration. This will be a
temporary increase as removing the legacy control group will remove the
legacy embeddable factory registration, which is larger then react
embeddable registration.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2024-08-09 12:29:40 -06:00 committed by GitHub
parent 60c5eca33e
commit 1616c3c22f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
114 changed files with 378 additions and 1102 deletions

View file

@ -1,129 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { getIpRangeQuery, getIpSegments, getMinMaxIp } from './ip_search';
describe('test IP search functionality', () => {
test('get IP segments', () => {
expect(getIpSegments('')).toStrictEqual({ segments: [''], type: 'unknown' });
expect(getIpSegments('test')).toStrictEqual({ segments: ['test'], type: 'unknown' });
expect(getIpSegments('123.456')).toStrictEqual({ segments: ['123', '456'], type: 'ipv4' });
expect(getIpSegments('123..456...')).toStrictEqual({ segments: ['123', '456'], type: 'ipv4' });
expect(getIpSegments('abc:def:')).toStrictEqual({ segments: ['abc', 'def'], type: 'ipv6' });
expect(getIpSegments(':::x:::abc:::def:::')).toStrictEqual({
segments: ['x', 'abc', 'def'],
type: 'ipv6',
});
});
test('get min/max IP', () => {
expect(getMinMaxIp('ipv4', ['123'])).toStrictEqual({
min: '123.0.0.0',
max: '123.255.255.255',
});
expect(getMinMaxIp('ipv4', ['123', '456', '789'])).toStrictEqual({
min: '123.456.789.0',
max: '123.456.789.255',
});
expect(getMinMaxIp('ipv6', ['abc', 'def'])).toStrictEqual({
min: 'abc:def::',
max: 'abc:def:ffff:ffff:ffff:ffff:ffff:ffff',
});
expect(getMinMaxIp('ipv6', ['a', 'b', 'c', 'd', 'e', 'f', 'g'])).toStrictEqual({
min: 'a:b:c:d:e:f:g::',
max: 'a:b:c:d:e:f:g:ffff',
});
});
test('get IP range query', () => {
// invalid searches
expect(getIpRangeQuery('xyz')).toStrictEqual({
validSearch: false,
});
expect(getIpRangeQuery('123.456.OVER 9000')).toStrictEqual({
validSearch: false,
});
expect(getIpRangeQuery('abc:def:ghi')).toStrictEqual({
validSearch: false,
});
// full IP searches
expect(getIpRangeQuery('1.2.3.4')).toStrictEqual({
validSearch: true,
rangeQuery: [
{
key: 'ipv4',
mask: '1.2.3.4/32',
},
],
});
expect(getIpRangeQuery('1.2.3.256')).toStrictEqual({
validSearch: false,
rangeQuery: undefined,
});
expect(getIpRangeQuery('fbbe:a363:9e14:987c:49cf:d4d0:d8c8:bc42')).toStrictEqual({
validSearch: true,
rangeQuery: [
{
key: 'ipv6',
mask: 'fbbe:a363:9e14:987c:49cf:d4d0:d8c8:bc42/128',
},
],
});
// partial IP searches - ipv4
const partialIpv4 = getIpRangeQuery('12.34.');
expect(partialIpv4.validSearch).toBe(true);
expect(partialIpv4.rangeQuery?.[0]).toStrictEqual({
key: 'ipv4',
from: '12.34.0.0',
to: '12.34.255.255',
});
expect(getIpRangeQuery('123.456.7')).toStrictEqual({
validSearch: false,
rangeQuery: [],
});
expect(getIpRangeQuery('12:34.56')).toStrictEqual({
validSearch: false,
rangeQuery: [],
});
// partial IP searches - ipv6
const partialIpv6 = getIpRangeQuery('fbbe:a363:9e14:987c:49cf');
expect(partialIpv6.validSearch).toBe(true);
expect(partialIpv6.rangeQuery?.[0]).toStrictEqual({
key: 'ipv6',
from: 'fbbe:a363:9e14:987c:49cf::',
to: 'fbbe:a363:9e14:987c:49cf:ffff:ffff:ffff',
});
// partial IP searches - unknown type
let partialUnknownIp = getIpRangeQuery('1234');
expect(partialUnknownIp.validSearch).toBe(true);
expect(partialUnknownIp.rangeQuery?.length).toBe(1);
expect(partialUnknownIp.rangeQuery?.[0]).toStrictEqual({
key: 'ipv6',
from: '1234::',
to: '1234:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
});
partialUnknownIp = getIpRangeQuery('123');
expect(partialUnknownIp.validSearch).toBe(true);
expect(partialUnknownIp.rangeQuery?.length).toBe(2);
expect(partialUnknownIp.rangeQuery?.[0]).toStrictEqual({
key: 'ipv4',
from: '123.0.0.0',
to: '123.255.255.255',
});
expect(partialUnknownIp.rangeQuery?.[1]).toStrictEqual({
key: 'ipv6',
from: '123::',
to: '123:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
});
});
});

View file

@ -1,122 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import ipaddr from 'ipaddr.js';
export interface IpRangeQuery {
validSearch: boolean;
rangeQuery?: Array<{ key: string; from: string; to: string } | { key: string; mask: string }>;
}
interface IpSegments {
segments: string[];
type: 'ipv4' | 'ipv6' | 'unknown';
}
export const getIsValidFullIp = (searchString: string) => {
return ipaddr.IPv4.isValidFourPartDecimal(searchString) || ipaddr.IPv6.isValid(searchString);
};
export const getIpSegments = (searchString: string): IpSegments => {
if (searchString.indexOf('.') !== -1) {
// ipv4 takes priority - so if search string contains both `.` and `:` then it will just be an invalid ipv4 search
const ipv4Segments = searchString.split('.').filter((segment) => segment !== '');
return { segments: ipv4Segments, type: 'ipv4' };
} else if (searchString.indexOf(':') !== -1) {
// note that currently, because of the logic of splitting here, searching for shorthand IPv6 IPs is not supported (for example,
// must search for `59fb:0:0:0:0:1005:cc57:6571` and not `59fb::1005:cc57:6571` to get the expected match)
const ipv6Segments = searchString.split(':').filter((segment) => segment !== '');
return { segments: ipv6Segments, type: 'ipv6' };
}
return { segments: [searchString], type: 'unknown' };
};
export const getMinMaxIp = (
type: 'ipv4' | 'ipv6',
segments: IpSegments['segments']
): { min: string; max: string } => {
const isIpv4 = type === 'ipv4';
const minIp = isIpv4
? segments.concat(Array(4 - segments.length).fill('0')).join('.')
: segments.join(':') + '::';
const maxIp = isIpv4
? segments.concat(Array(4 - segments.length).fill('255')).join('.')
: segments.concat(Array(8 - segments.length).fill('ffff')).join(':');
return {
min: minIp,
max: maxIp,
};
};
const buildFullIpSearchRangeQuery = (segments: IpSegments): IpRangeQuery['rangeQuery'] => {
const { type: ipType, segments: ipSegments } = segments;
const isIpv4 = ipType === 'ipv4';
const searchIp = ipSegments.join(isIpv4 ? '.' : ':');
if (ipaddr.isValid(searchIp)) {
return [
{
key: ipType,
mask: isIpv4 ? searchIp + '/32' : searchIp + '/128',
},
];
}
return undefined;
};
const buildPartialIpSearchRangeQuery = (segments: IpSegments): IpRangeQuery['rangeQuery'] => {
const { type: ipType, segments: ipSegments } = segments;
const ranges = [];
if (ipType === 'unknown' || ipType === 'ipv4') {
const { min: minIpv4, max: maxIpv4 } = getMinMaxIp('ipv4', ipSegments);
if (ipaddr.isValid(minIpv4) && ipaddr.isValid(maxIpv4)) {
ranges.push({
key: 'ipv4',
from: minIpv4,
to: maxIpv4,
});
}
}
if (ipType === 'unknown' || ipType === 'ipv6') {
const { min: minIpv6, max: maxIpv6 } = getMinMaxIp('ipv6', ipSegments);
if (ipaddr.isValid(minIpv6) && ipaddr.isValid(maxIpv6)) {
ranges.push({
key: 'ipv6',
from: minIpv6,
to: maxIpv6,
});
}
}
return ranges;
};
export const getIpRangeQuery = (searchString: string): IpRangeQuery => {
if (searchString.match(/^[A-Fa-f0-9.:]*$/) === null) {
return { validSearch: false };
}
const ipSegments = getIpSegments(searchString);
if (ipSegments.type === 'ipv4' && ipSegments.segments.length === 4) {
const ipv4RangeQuery = buildFullIpSearchRangeQuery(ipSegments);
return { validSearch: Boolean(ipv4RangeQuery), rangeQuery: ipv4RangeQuery };
}
if (ipSegments.type === 'ipv6' && ipSegments.segments.length === 8) {
const ipv6RangeQuery = buildFullIpSearchRangeQuery(ipSegments);
return { validSearch: Boolean(ipv6RangeQuery), rangeQuery: ipv6RangeQuery };
}
const partialRangeQuery = buildPartialIpSearchRangeQuery(ipSegments);
return {
validSearch: !(partialRangeQuery?.length === 0),
rangeQuery: partialRangeQuery,
};
};

View file

@ -1,19 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { DataViewField } from '@kbn/data-views-plugin/common';
export type OptionsListSelection = string | number;
export const getSelectionAsFieldType = (
field: DataViewField,
key: string
): OptionsListSelection => {
const storeAsNumber = field.type === 'number' || field.type === 'date';
return storeAsNumber ? +key : key;
};

View file

@ -1,118 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { isValidSearch } from './suggestions_searching';
describe('test validity of search strings', () => {
describe('number field', () => {
it('valid search - basic integer', () => {
expect(isValidSearch({ searchString: '123', fieldType: 'number' })).toBe(true);
});
it('valid search - floating point number', () => {
expect(isValidSearch({ searchString: '12.34', fieldType: 'number' })).toBe(true);
});
it('valid search - negative number', () => {
expect(isValidSearch({ searchString: '-42', fieldType: 'number' })).toBe(true);
});
it('invalid search - invalid character search string', () => {
expect(isValidSearch({ searchString: '1!a23', fieldType: 'number' })).toBe(false);
});
});
// we do not currently support searching date fields, so they will always be invalid
describe('date field', () => {
it('invalid search - formatted date', () => {
expect(isValidSearch({ searchString: 'December 12, 2023', fieldType: 'date' })).toBe(false);
});
it('invalid search - invalid character search string', () => {
expect(isValidSearch({ searchString: '!!12/12/23?', fieldType: 'date' })).toBe(false);
});
});
// only testing exact match validity here - the remainder of testing is covered by ./ip_search.test.ts
describe('ip field', () => {
it('valid search - ipv4', () => {
expect(
isValidSearch({
searchString: '1.2.3.4',
fieldType: 'ip',
searchTechnique: 'exact',
})
).toBe(true);
});
it('valid search - full ipv6', () => {
expect(
isValidSearch({
searchString: 'fbbe:a363:9e14:987c:49cf:d4d0:d8c8:bc42',
fieldType: 'ip',
searchTechnique: 'exact',
})
).toBe(true);
});
it('valid search - partial ipv6', () => {
expect(
isValidSearch({
searchString: 'fbbe:a363::',
fieldType: 'ip',
searchTechnique: 'exact',
})
).toBe(true);
});
it('invalid search - invalid character search string', () => {
expect(
isValidSearch({
searchString: '!!123.abc?',
fieldType: 'ip',
searchTechnique: 'exact',
})
).toBe(false);
});
it('invalid search - ipv4', () => {
expect(
isValidSearch({
searchString: '1.2.3.256',
fieldType: 'ip',
searchTechnique: 'exact',
})
).toBe(false);
});
it('invalid search - ipv6', () => {
expect(
isValidSearch({
searchString: '::fbbe:a363::',
fieldType: 'ip',
searchTechnique: 'exact',
})
).toBe(false);
});
});
// string field searches can never be invalid
describe('string field', () => {
it('valid search - basic search string', () => {
expect(isValidSearch({ searchString: 'abc', fieldType: 'string' })).toBe(true);
});
it('valid search - numeric search string', () => {
expect(isValidSearch({ searchString: '123', fieldType: 'string' })).toBe(true);
});
it('valid search - complex search string', () => {
expect(isValidSearch({ searchString: '!+@abc*&[]', fieldType: 'string' })).toBe(true);
});
});
});

View file

@ -1,69 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { getIpRangeQuery, getIsValidFullIp } from './ip_search';
export type OptionsListSearchTechnique = 'prefix' | 'wildcard' | 'exact';
export const getDefaultSearchTechnique = (type: string): OptionsListSearchTechnique | undefined => {
const compatibleSearchTechniques = getCompatibleSearchTechniques(type);
return compatibleSearchTechniques.length > 0 ? compatibleSearchTechniques[0] : undefined;
};
export const getCompatibleSearchTechniques = (type?: string): OptionsListSearchTechnique[] => {
switch (type) {
case 'string': {
return ['prefix', 'wildcard', 'exact'];
}
case 'ip': {
return ['prefix', 'exact'];
}
case 'number': {
return ['exact'];
}
default: {
return [];
}
}
};
export const isValidSearch = ({
searchString,
fieldType,
searchTechnique,
}: {
searchString?: string;
fieldType?: string;
searchTechnique?: OptionsListSearchTechnique;
}): boolean => {
if (!searchString || searchString.length === 0) return true;
switch (fieldType) {
case 'number': {
return !isNaN(Number(searchString));
}
case 'date': {
/** searching is not currently supported for date fields */
return false;
}
case 'ip': {
if (searchTechnique === 'exact') {
/**
* exact match searching will throw an error if the search string isn't a **full** IP,
* so we need a slightly different validity check here than for other search techniques
*/
return getIsValidFullIp(searchString);
}
return getIpRangeQuery(searchString).validSearch;
}
default: {
/** string searches are always considered to be valid */
return true;
}
}
};

View file

@ -1,32 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Direction } from '@elastic/eui';
export type OptionsListSortBy = '_count' | '_key';
export const OPTIONS_LIST_DEFAULT_SORT: OptionsListSortingType = {
by: '_count',
direction: 'desc',
};
export interface OptionsListSortingType {
by: OptionsListSortBy;
direction: Direction;
}
export const getCompatibleSortingTypes = (type?: string): OptionsListSortBy[] => {
switch (type) {
case 'ip': {
return ['_count'];
}
default: {
return ['_count', '_key'];
}
}
};

View file

@ -23,11 +23,8 @@ import {
EuiToolTip,
OnTimeChangeProps,
} from '@elastic/eui';
import {
CONTROL_GROUP_TYPE,
DEFAULT_CONTROL_GROW,
DEFAULT_CONTROL_WIDTH,
} from '@kbn/controls-plugin/common';
import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common';
import { ControlGroupApi } from '@kbn/controls-plugin/public';
import { CoreStart } from '@kbn/core/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { ReactEmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public';
@ -53,8 +50,6 @@ import {
getControlGroupRuntimeState,
setControlGroupRuntimeState,
} from './runtime_control_group_state';
import { ControlGroupApi } from '../../react_controls/control_group/types';
import { openDataControlEditor } from '../../react_controls/data_controls/open_data_control_editor';
const toggleViewButtons = [
{
@ -319,24 +314,7 @@ export const ReactControlExample = ({
<EuiFlexItem grow={false}>
<EuiButton
onClick={() => {
openDataControlEditor({
initialState: {
grow: DEFAULT_CONTROL_GROW,
width: DEFAULT_CONTROL_WIDTH,
dataViewId: dashboardApi.lastUsedDataViewId.getValue(),
},
onSave: ({ type: controlType, state: initialState }) => {
controlGroupApi.addNewPanel({
panelType: controlType,
initialState,
});
},
controlGroupApi,
services: {
core,
dataViews: dataViewsService,
},
});
controlGroupApi?.openAddDataControlFlyout();
}}
size="s"
>

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { ControlGroupRuntimeState } from '../../react_controls/control_group/types';
import { ControlGroupRuntimeState } from '@kbn/controls-plugin/public';
const RUNTIME_STATE_SESSION_STORAGE_KEY =
'kibana.examples.controls.reactControlExample.controlGroupRuntimeState';

View file

@ -7,11 +7,12 @@
*/
import { SerializedPanelState } from '@kbn/presentation-containers';
import { ControlGroupSerializedState } from '../../react_controls/control_group/types';
import { OPTIONS_LIST_CONTROL_TYPE } from '../../react_controls/data_controls/options_list_control/constants';
import { RANGE_SLIDER_CONTROL_TYPE } from '../../react_controls/data_controls/range_slider/types';
import { SEARCH_CONTROL_TYPE } from '../../react_controls/data_controls/search_control/types';
import { TIMESLIDER_CONTROL_TYPE } from '../../react_controls/timeslider_control/types';
import { ControlGroupSerializedState } from '@kbn/controls-plugin/public';
import {
OPTIONS_LIST_CONTROL,
RANGE_SLIDER_CONTROL,
TIME_SLIDER_CONTROL,
} from '@kbn/controls-plugin/common';
const SERIALIZED_STATE_SESSION_STORAGE_KEY =
'kibana.examples.controls.reactControlExample.controlGroupSerializedState';
@ -37,22 +38,8 @@ const searchControlId = 'searchControl1';
const rangeSliderControlId = 'rangeSliderControl1';
const timesliderControlId = 'timesliderControl1';
const controlGroupPanels = {
[searchControlId]: {
type: SEARCH_CONTROL_TYPE,
order: 3,
grow: true,
width: 'medium',
explicitInput: {
id: searchControlId,
fieldName: 'message',
title: 'Message',
grow: true,
width: 'medium',
enhancements: {},
},
},
[rangeSliderControlId]: {
type: RANGE_SLIDER_CONTROL_TYPE,
type: RANGE_SLIDER_CONTROL,
order: 1,
grow: true,
width: 'medium',
@ -66,7 +53,7 @@ const controlGroupPanels = {
},
},
[timesliderControlId]: {
type: TIMESLIDER_CONTROL_TYPE,
type: TIME_SLIDER_CONTROL,
order: 4,
grow: true,
width: 'medium',
@ -77,7 +64,7 @@ const controlGroupPanels = {
},
},
[optionsListId]: {
type: OPTIONS_LIST_CONTROL_TYPE,
type: OPTIONS_LIST_CONTROL,
order: 2,
grow: true,
width: 'medium',
@ -103,17 +90,12 @@ const initialSerializedControlGroupState = {
} as object,
references: [
{
name: `controlGroup_${searchControlId}:${SEARCH_CONTROL_TYPE}DataView`,
name: `controlGroup_${rangeSliderControlId}:${RANGE_SLIDER_CONTROL}DataView`,
type: 'index-pattern',
id: WEB_LOGS_DATA_VIEW_ID,
},
{
name: `controlGroup_${rangeSliderControlId}:${RANGE_SLIDER_CONTROL_TYPE}DataView`,
type: 'index-pattern',
id: WEB_LOGS_DATA_VIEW_ID,
},
{
name: `controlGroup_${optionsListId}:optionsListControlDataView`,
name: `controlGroup_${optionsListId}:${OPTIONS_LIST_CONTROL}DataView`,
type: 'index-pattern',
id: WEB_LOGS_DATA_VIEW_ID,
},

View file

@ -6,25 +6,16 @@
* Side Public License, v 1.
*/
import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { EmbeddableSetup, PANEL_HOVER_TRIGGER } from '@kbn/embeddable-plugin/public';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { PLUGIN_ID } from './constants';
import img from './control_group_image.png';
import { EditControlAction } from './react_controls/actions/edit_control_action';
import { registerControlFactory } from './react_controls/control_factory_registry';
import { OPTIONS_LIST_CONTROL_TYPE } from './react_controls/data_controls/options_list_control/constants';
import { RANGE_SLIDER_CONTROL_TYPE } from './react_controls/data_controls/range_slider/types';
import { SEARCH_CONTROL_TYPE } from './react_controls/data_controls/search_control/types';
import { TIMESLIDER_CONTROL_TYPE } from './react_controls/timeslider_control/types';
interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
embeddable: EmbeddableSetup;
}
export interface ControlsExampleStartDeps {
@ -36,73 +27,7 @@ export interface ControlsExampleStartDeps {
export class ControlsExamplePlugin
implements Plugin<void, void, SetupDeps, ControlsExampleStartDeps>
{
public setup(
core: CoreSetup<ControlsExampleStartDeps>,
{ developerExamples, embeddable }: SetupDeps
) {
embeddable.registerReactEmbeddableFactory(CONTROL_GROUP_TYPE, async () => {
const [{ getControlGroupEmbeddableFactory }, [coreStart, depsStart]] = await Promise.all([
import('./react_controls/control_group/get_control_group_factory'),
core.getStartServices(),
]);
return getControlGroupEmbeddableFactory({
core: coreStart,
dataViews: depsStart.data.dataViews,
});
});
registerControlFactory(OPTIONS_LIST_CONTROL_TYPE, async () => {
const [{ getOptionsListControlFactory }, [coreStart, depsStart]] = await Promise.all([
import(
'./react_controls/data_controls/options_list_control/get_options_list_control_factory'
),
core.getStartServices(),
]);
return getOptionsListControlFactory({
core: coreStart,
data: depsStart.data,
dataViews: depsStart.data.dataViews,
});
});
registerControlFactory(SEARCH_CONTROL_TYPE, async () => {
const [{ getSearchControlFactory: getSearchEmbeddableFactory }, [coreStart, depsStart]] =
await Promise.all([
import('./react_controls/data_controls/search_control/get_search_control_factory'),
core.getStartServices(),
]);
return getSearchEmbeddableFactory({
core: coreStart,
data: depsStart.data,
dataViews: depsStart.data.dataViews,
});
});
registerControlFactory(RANGE_SLIDER_CONTROL_TYPE, async () => {
const [{ getRangesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([
import('./react_controls/data_controls/range_slider/get_range_slider_control_factory'),
core.getStartServices(),
]);
return getRangesliderControlFactory({
core: coreStart,
data: depsStart.data,
dataViews: depsStart.data.dataViews,
});
});
registerControlFactory(TIMESLIDER_CONTROL_TYPE, async () => {
const [{ getTimesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([
import('./react_controls/timeslider_control/get_timeslider_control_factory'),
core.getStartServices(),
]);
return getTimesliderControlFactory({
core: coreStart,
data: depsStart.data,
});
});
public setup(core: CoreSetup<ControlsExampleStartDeps>, { developerExamples }: SetupDeps) {
core.application.register({
id: PLUGIN_ID,
title: 'Controls examples',
@ -122,11 +47,7 @@ export class ControlsExamplePlugin
});
}
public start(core: CoreStart, deps: ControlsExampleStartDeps) {
const editControlAction = new EditControlAction();
deps.uiActions.registerAction(editControlAction);
deps.uiActions.attachAction(PANEL_HOVER_TRIGGER, editControlAction.id);
}
public start(core: CoreStart, deps: ControlsExampleStartDeps) {}
public stop() {}
}

View file

@ -1,226 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { useEffect, useState } from 'react';
import { BehaviorSubject, combineLatest, debounceTime, skip } from 'rxjs';
import { EuiFieldSearch, EuiFormRow, EuiRadioGroup } from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { useStateFromPublishingSubject } from '@kbn/presentation-publishing';
import { euiThemeVars } from '@kbn/ui-theme';
import { Filter } from '@kbn/es-query';
import { initializeDataControl } from '../initialize_data_control';
import { DataControlFactory, DataControlServices } from '../types';
import {
SearchControlApi,
SearchControlState,
SearchControlTechniques,
SEARCH_CONTROL_TYPE,
} from './types';
import { initializeSearchControlSelections } from './search_control_selections';
const allSearchOptions = [
{
id: 'match',
label: i18n.translate('controlsExamples.searchControl.searchTechnique.match', {
defaultMessage: 'Fuzzy match',
}),
'data-test-subj': 'searchControl__matchSearchOptionAdditionalSetting',
},
{
id: 'simple_query_string',
label: i18n.translate('controlsExamples.searchControl.searchTechnique.simpleQueryString', {
defaultMessage: 'Query string',
}),
'data-test-subj': 'optionsListControl__queryStringSearchOptionAdditionalSetting',
},
];
const DEFAULT_SEARCH_TECHNIQUE = 'match';
export const getSearchControlFactory = (
services: DataControlServices
): DataControlFactory<SearchControlState, SearchControlApi> => {
return {
type: SEARCH_CONTROL_TYPE,
getIconType: () => 'search',
getDisplayName: () =>
i18n.translate('controlsExamples.searchControl.displayName', { defaultMessage: 'Search' }),
isFieldCompatible: (field) => {
return (
field.searchable &&
field.spec.type === 'string' &&
(field.spec.esTypes ?? []).includes('text')
);
},
CustomOptionsComponent: ({ initialState, updateState }) => {
const [searchTechnique, setSearchTechnique] = useState(
initialState.searchTechnique ?? DEFAULT_SEARCH_TECHNIQUE
);
return (
<EuiFormRow label={'Searching'} data-test-subj="searchControl__searchOptionsRadioGroup">
<EuiRadioGroup
options={allSearchOptions}
idSelected={searchTechnique}
onChange={(id) => {
const newSearchTechnique = id as SearchControlTechniques;
setSearchTechnique(newSearchTechnique);
updateState({ searchTechnique: newSearchTechnique });
}}
/>
</EuiFormRow>
);
},
buildControl: async (initialState, buildApi, uuid, parentApi) => {
const searchTechnique = new BehaviorSubject<SearchControlTechniques | undefined>(
initialState.searchTechnique ?? DEFAULT_SEARCH_TECHNIQUE
);
const editorStateManager = { searchTechnique };
const dataControl = initializeDataControl<Pick<SearchControlState, 'searchTechnique'>>(
uuid,
SEARCH_CONTROL_TYPE,
initialState,
editorStateManager,
parentApi,
services
);
const selections = initializeSearchControlSelections(
initialState,
dataControl.setters.onSelectionChange
);
const api = buildApi(
{
...dataControl.api,
getTypeDisplayName: () =>
i18n.translate('controlsExamples.searchControl.displayName', {
defaultMessage: 'Search',
}),
serializeState: () => {
const { rawState: dataControlState, references } = dataControl.serialize();
return {
rawState: {
...dataControlState,
searchString: selections.searchString$.getValue(),
searchTechnique: searchTechnique.getValue(),
},
references, // does not have any references other than those provided by the data control serializer
};
},
clearSelections: () => {
selections.setSearchString(undefined);
},
},
{
...dataControl.comparators,
...selections.comparators,
searchTechnique: [
searchTechnique,
(newTechnique: SearchControlTechniques | undefined) =>
searchTechnique.next(newTechnique),
(a, b) => (a ?? DEFAULT_SEARCH_TECHNIQUE) === (b ?? DEFAULT_SEARCH_TECHNIQUE),
],
}
);
/**
* If either the search string or the search technique changes, recalulate the output filter
*/
const onSearchStringChanged = combineLatest([selections.searchString$, searchTechnique])
.pipe(debounceTime(200))
.subscribe(([newSearchString, currentSearchTechnnique]) => {
const currentDataView = dataControl.api.dataViews.getValue()?.[0];
const currentField = dataControl.stateManager.fieldName.getValue();
let filter: Filter | undefined;
if (currentDataView && currentField && newSearchString) {
filter =
currentSearchTechnnique === 'match'
? {
query: { match: { [currentField]: { query: newSearchString } } },
meta: { index: currentDataView.id },
}
: {
query: {
simple_query_string: {
query: newSearchString,
fields: [currentField],
default_operator: 'and',
},
},
meta: { index: currentDataView.id },
};
}
dataControl.setters.setOutputFilter(filter);
});
/**
* When the field changes (which can happen if either the field name or the dataview id changes),
* clear the previous search string.
*/
const onFieldChanged = combineLatest([
dataControl.stateManager.fieldName,
dataControl.stateManager.dataViewId,
])
.pipe(skip(1))
.subscribe(() => {
selections.setSearchString(undefined);
});
if (initialState.searchString?.length) {
await dataControl.api.untilFiltersReady();
}
return {
api,
/**
* The `controlPanelClassNamess` prop is necessary because it contains the class names from the generic
* ControlPanel that are necessary for styling
*/
Component: ({ className: controlPanelClassName }) => {
const currentSearch = useStateFromPublishingSubject(selections.searchString$);
useEffect(() => {
return () => {
// cleanup on unmount
dataControl.cleanup();
onSearchStringChanged.unsubscribe();
onFieldChanged.unsubscribe();
};
}, []);
return (
<EuiFieldSearch
className={controlPanelClassName}
css={css`
height: calc(${euiThemeVars.euiButtonHeight} - 2px) !important;
`}
incremental={true}
isClearable={false} // this will be handled by the clear floating action instead
value={currentSearch ?? ''}
onChange={(event) => {
selections.setSearchString(event.target.value);
}}
placeholder={i18n.translate('controls.searchControl.placeholder', {
defaultMessage: 'Search...',
})}
id={uuid}
fullWidth
/>
);
},
};
},
};
};

View file

@ -1,33 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { BehaviorSubject } from 'rxjs';
import { PublishingSubject, StateComparators } from '@kbn/presentation-publishing';
import { SearchControlState } from './types';
export function initializeSearchControlSelections(
initialState: SearchControlState,
onSelectionChange: () => void
) {
const searchString$ = new BehaviorSubject<string | undefined>(initialState.searchString);
function setSearchString(next: string | undefined) {
if (searchString$.value !== next) {
searchString$.next(next);
onSelectionChange();
}
}
return {
comparators: {
searchString: [searchString$, setSearchString],
} as StateComparators<Pick<SearchControlState, 'searchString'>>,
hasInitialSelections: initialState.searchString?.length,
searchString$: searchString$ as PublishingSubject<string | undefined>,
setSearchString,
};
}

View file

@ -1,20 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { DataControlApi, DefaultDataControlState } from '../types';
export const SEARCH_CONTROL_TYPE = 'searchControl';
export type SearchControlTechniques = 'match' | 'simple_query_string';
export interface SearchControlState extends DefaultDataControlState {
searchString?: string;
searchTechnique?: SearchControlTechniques;
}
export type SearchControlApi = DataControlApi;

View file

@ -24,18 +24,7 @@
"@kbn/presentation-containers",
"@kbn/presentation-publishing",
"@kbn/ui-actions-plugin",
"@kbn/i18n-react",
"@kbn/shared-ux-markdown",
"@kbn/i18n",
"@kbn/core-mount-utils-browser",
"@kbn/react-kibana-mount",
"@kbn/content-management-utils",
"@kbn/presentation-util-plugin",
"@kbn/core-lifecycle-browser",
"@kbn/presentation-panel-plugin",
"@kbn/datemath",
"@kbn/ui-theme",
"@kbn/react-kibana-context-render",
"@kbn/field-formats-plugin",
]
}

View file

@ -20,7 +20,7 @@ pageLoadAssetSize:
cloudSecurityPosture: 19109
console: 46091
contentManagement: 16254
controls: 55082
controls: 60000
core: 435325
crossClusterReplication: 65408
customIntegrations: 22034

View file

@ -12,7 +12,7 @@ import { OPTIONS_LIST_CONTROL } from '../../../common';
import { ControlOutput } from '../../types';
import { ControlGroupInput } from '../types';
import { pluginServices } from '../../services';
import { EditControlAction } from './edit_control_action';
import { EditLegacyEmbeddableControlAction } from './edit_control_action';
import { DeleteControlAction } from './delete_control_action';
import { TimeSliderEmbeddableFactory } from '../../time_slider';
import { OptionsListEmbeddableFactory, OptionsListEmbeddableInput } from '../../options_list';
@ -24,7 +24,7 @@ const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroup
const deleteControlAction = new DeleteControlAction();
test('Action is incompatible with Error Embeddables', async () => {
const editControlAction = new EditControlAction(deleteControlAction);
const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction);
const errorEmbeddable = new ErrorEmbeddable('Wow what an awful error', { id: ' 404' });
expect(await editControlAction.isCompatible({ embeddable: errorEmbeddable as any })).toBe(false);
});
@ -35,7 +35,7 @@ test('Action is incompatible with embeddables that are not editable', async () =
pluginServices.getServices().controls.getControlFactory = mockGetFactory;
pluginServices.getServices().embeddable.getEmbeddableFactory = mockGetFactory;
const editControlAction = new EditControlAction(deleteControlAction);
const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction);
const emptyContainer = new ControlGroupContainer(mockedReduxEmbeddablePackage, controlGroupInput);
await emptyContainer.untilInitialized();
await emptyContainer.addTimeSliderControl();
@ -53,7 +53,7 @@ test('Action is compatible with embeddables that are editable', async () => {
pluginServices.getServices().controls.getControlFactory = mockGetFactory;
pluginServices.getServices().embeddable.getEmbeddableFactory = mockGetFactory;
const editControlAction = new EditControlAction(deleteControlAction);
const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction);
const emptyContainer = new ControlGroupContainer(mockedReduxEmbeddablePackage, controlGroupInput);
await emptyContainer.untilInitialized();
const control = await emptyContainer.addOptionsListControl({
@ -73,7 +73,7 @@ test('Action is compatible with embeddables that are editable', async () => {
});
test('Execute throws an error when called with an embeddable not in a parent', async () => {
const editControlAction = new EditControlAction(deleteControlAction);
const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction);
const optionsListEmbeddable = new OptionsListEmbeddable(
mockedReduxEmbeddablePackage,
{} as OptionsListEmbeddableInput,
@ -99,7 +99,7 @@ test('Execute should open a flyout', async () => {
})) as OptionsListEmbeddable;
expect(emptyContainer.getInput().panels[control.getInput().id].type).toBe(OPTIONS_LIST_CONTROL);
const editControlAction = new EditControlAction(deleteControlAction);
const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction);
await editControlAction.execute({ embeddable: control });
expect(spyOn).toHaveBeenCalled();
});

View file

@ -26,7 +26,7 @@ export interface EditControlActionContext {
embeddable: ControlEmbeddable<DataControlInput>;
}
export class EditControlAction implements Action<EditControlActionContext> {
export class EditLegacyEmbeddableControlAction implements Action<EditControlActionContext> {
public readonly type = ACTION_EDIT_CONTROL;
public readonly id = ACTION_EDIT_CONTROL;
public order = 2;

View file

@ -6,6 +6,6 @@
* Side Public License, v 1.
*/
export const ACTION_EDIT_CONTROL = 'editControl';
export const ACTION_EDIT_CONTROL = 'editLegacyEmbeddableControl';
export const ACTION_CLEAR_CONTROL = 'clearControl';
export const ACTION_DELETE_CONTROL = 'deleteControl';

View file

@ -8,6 +8,22 @@
import { ControlsPlugin } from './plugin';
export type {
ControlGroupApi,
ControlGroupRuntimeState,
ControlGroupSerializedState,
} from './react_controls/control_group/types';
export type {
DataControlApi,
DefaultDataControlState,
DataControlFactory,
DataControlServices,
} from './react_controls/controls/data_controls/types';
/**
* TODO: remove all exports below this when control group embeddable is removed
*/
export type {
ControlOutput,
ControlFactory,

View file

@ -28,6 +28,11 @@ import {
IEditableControlFactory,
ControlInput,
} from './types';
import { registerControlGroupEmbeddable } from './react_controls/control_group/register_control_group_embeddable';
import { registerOptionsListControl } from './react_controls/controls/data_controls/options_list_control/register_options_list_control';
import { registerRangeSliderControl } from './react_controls/controls/data_controls/range_slider/register_range_slider_control';
import { registerTimeSliderControl } from './react_controls/controls/timeslider_control/register_timeslider_control';
import { EditControlAction } from './react_controls/actions/edit_control_action/edit_control_action';
export class ControlsPlugin
implements
Plugin<
@ -63,6 +68,11 @@ export class ControlsPlugin
const { registerControlType } = controlsService;
const { embeddable } = _setupPlugins;
registerControlGroupEmbeddable(_coreSetup, embeddable);
registerOptionsListControl(_coreSetup);
registerRangeSliderControl(_coreSetup);
registerTimeSliderControl(_coreSetup);
// register control group embeddable factory
_coreSetup.getStartServices().then(([, deps]) => {
embeddable.registerEmbeddableFactory(
@ -120,11 +130,22 @@ export class ControlsPlugin
uiActions.registerAction(deleteControlAction);
uiActions.attachAction(PANEL_HOVER_TRIGGER, deleteControlAction.id);
const { EditControlAction } = await import('./control_group/actions/edit_control_action');
const editControlAction = new EditControlAction(deleteControlAction);
const editControlAction = new EditControlAction();
uiActions.registerAction(editControlAction);
uiActions.attachAction(PANEL_HOVER_TRIGGER, editControlAction.id);
/**
* TODO: Remove edit legacy control embeddable action when embeddable controls are removed
*/
const { EditLegacyEmbeddableControlAction } = await import(
'./control_group/actions/edit_control_action'
);
const editLegacyEmbeddableControlAction = new EditLegacyEmbeddableControlAction(
deleteControlAction
);
uiActions.registerAction(editLegacyEmbeddableControlAction);
uiActions.attachAction(PANEL_HOVER_TRIGGER, editLegacyEmbeddableControlAction.id);
const { ClearControlAction } = await import('./control_group/actions/clear_control_action');
const clearControlAction = new ClearControlAction();
uiActions.registerAction(clearControlAction);

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
import {
apiCanAccessViewMode,
apiHasParentApi,
apiHasType,
apiHasUniqueId,
apiIsOfType,
getInheritedViewMode,
hasEditCapabilities,
} from '@kbn/presentation-publishing';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { CONTROL_GROUP_TYPE } from '../../../../common';
import { DataControlApi } from '../../controls/data_controls/types';
export const compatibilityCheck = (api: unknown): api is DataControlApi => {
return Boolean(
apiHasType(api) &&
apiHasUniqueId(api) &&
hasEditCapabilities(api) &&
apiHasParentApi(api) &&
apiCanAccessViewMode(api.parentApi) &&
apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) &&
apiIsPresentationContainer(api.parentApi)
);
};
export function isCompatible(api: unknown) {
return (
compatibilityCheck(api) &&
getInheritedViewMode(api.parentApi) === ViewMode.EDIT &&
api.isEditingEnabled()
);
}

View file

@ -9,34 +9,9 @@
import React from 'react';
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
import {
apiCanAccessViewMode,
apiHasParentApi,
apiHasType,
apiHasUniqueId,
apiIsOfType,
EmbeddableApiContext,
getInheritedViewMode,
hasEditCapabilities,
} from '@kbn/presentation-publishing';
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { DataControlApi } from '../data_controls/types';
const isApiCompatible = (api: unknown | null): api is DataControlApi =>
Boolean(
apiHasType(api) &&
apiHasUniqueId(api) &&
hasEditCapabilities(api) &&
apiHasParentApi(api) &&
apiCanAccessViewMode(api.parentApi) &&
apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) &&
apiIsPresentationContainer(api.parentApi)
);
import type { EmbeddableApiContext, HasUniqueId } from '@kbn/presentation-publishing';
import { type Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
const ACTION_EDIT_CONTROL = 'editDataControl';
@ -48,11 +23,10 @@ export class EditControlAction implements Action<EmbeddableApiContext> {
constructor() {}
public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => {
if (!isApiCompatible(context.embeddable)) throw new IncompatibleActionError();
return (
<EuiToolTip content={this.getDisplayName(context)}>
<EuiButtonIcon
data-test-subj={`control-action-${context.embeddable.uuid}-edit`}
data-test-subj={`control-action-${(context.embeddable as HasUniqueId).uuid}-edit`}
aria-label={this.getDisplayName(context)}
iconType={this.getIconType(context)}
onClick={() => this.execute(context)}
@ -63,27 +37,23 @@ export class EditControlAction implements Action<EmbeddableApiContext> {
};
public getDisplayName({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
return i18n.translate('controls.controlGroup.floatingActions.editTitle', {
defaultMessage: 'Edit',
});
}
public getIconType({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
return 'pencil';
}
public async isCompatible({ embeddable }: EmbeddableApiContext) {
return (
isApiCompatible(embeddable) &&
getInheritedViewMode(embeddable.parentApi) === ViewMode.EDIT &&
embeddable.isEditingEnabled()
);
const { isCompatible } = await import('./compatibility_check');
return isCompatible(embeddable);
}
public async execute({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
const { compatibilityCheck } = await import('./compatibility_check');
if (!compatibilityCheck(embeddable)) throw new IncompatibleActionError();
await embeddable.onEdit();
}
}

View file

@ -7,7 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { ControlFactory, DefaultControlApi } from './types';
import { ControlFactory, DefaultControlApi } from './controls/types';
const registry: { [key: string]: ControlFactory<any, any> } = {};
@ -20,7 +20,7 @@ export const registerControlFactory = async <
) => {
if (registry[type] !== undefined)
throw new Error(
i18n.translate('controlFactoryRegistry.factoryAlreadyExistsError', {
i18n.translate('controls.controlFactoryRegistry.factoryAlreadyExistsError', {
defaultMessage: 'A control factory for type: {key} is already registered.',
values: { key: type },
})
@ -36,7 +36,7 @@ export const getControlFactory = <
): ControlFactory<State, ApiType> => {
if (registry[key] === undefined)
throw new Error(
i18n.translate('controlFactoryRegistry.factoryNotFoundError', {
i18n.translate('controls.controlFactoryRegistry.factoryNotFoundError', {
defaultMessage: 'No control factory found for type: {key}',
values: { key },
})

View file

@ -11,10 +11,10 @@ import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiFormLabel, EuiIcon } from '@elastic/eui';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import { DEFAULT_CONTROL_GROW } from '@kbn/controls-plugin/common';
import { BehaviorSubject } from 'rxjs';
import { DefaultControlApi } from '../types';
import { DEFAULT_CONTROL_GROW } from '../../../../common';
import { DefaultControlApi } from '../../controls/types';
/**
* A simplified clone version of the control which is dragged. This version only shows

View file

@ -32,13 +32,13 @@ import {
EuiPanel,
EuiToolTip,
} from '@elastic/eui';
import { ControlStyle } from '@kbn/controls-plugin/public';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import { ControlStyle } from '../../..';
import { ControlsInOrder } from '../init_controls_manager';
import { ControlGroupApi } from '../types';
import { ControlRenderer } from '../../control_renderer';
import { ControlClone } from '../../components/control_clone';
import { DefaultControlApi } from '../../types';
import { ControlRenderer } from './control_renderer';
import { ControlClone } from './control_clone';
import { DefaultControlApi } from '../../controls/types';
import { ControlGroupStrings } from '../control_group_strings';
interface Props {

View file

@ -19,7 +19,6 @@ import {
EuiIcon,
EuiToolTip,
} from '@elastic/eui';
import { DEFAULT_CONTROL_WIDTH } from '@kbn/controls-plugin/common';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import {
@ -28,8 +27,9 @@ import {
useBatchedOptionalPublishingSubjects,
} from '@kbn/presentation-publishing';
import { FloatingActions } from '@kbn/presentation-util-plugin/public';
import { DEFAULT_CONTROL_WIDTH } from '../../../../common';
import { ControlPanelProps, DefaultControlApi } from '../types';
import { ControlPanelProps, DefaultControlApi } from '../../controls/types';
import { ControlError } from './control_error';
import './control_panel.scss';

View file

@ -12,10 +12,14 @@ import { BehaviorSubject } from 'rxjs';
import { initializeUnsavedChanges } from '@kbn/presentation-containers';
import { StateComparators } from '@kbn/presentation-publishing';
import { getControlFactory } from './control_factory_registry';
import { ControlGroupApi } from './control_group/types';
import { ControlPanel } from './components/control_panel';
import { ControlApiRegistration, DefaultControlApi, DefaultControlState } from './types';
import { getControlFactory } from '../../control_factory_registry';
import { ControlGroupApi } from '../types';
import { ControlPanel } from './control_panel';
import {
ControlApiRegistration,
DefaultControlApi,
DefaultControlState,
} from '../../controls/types';
/**
* Renders a component from the control registry into a Control Panel

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { ControlGroupChainingSystem } from '@kbn/controls-plugin/common';
import { ControlGroupChainingSystem } from '../../../../common';
import { Filter } from '@kbn/es-query';
import { BehaviorSubject, skip } from 'rxjs';
import { chaining$ } from './chaining';

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
import { ControlGroupChainingSystem } from '@kbn/controls-plugin/common';
import { Filter, TimeRange } from '@kbn/es-query';
import {
apiPublishesFilters,
@ -22,6 +21,7 @@ import {
skipWhile,
switchMap,
} from 'rxjs';
import { ControlGroupChainingSystem } from '../../../../common';
export interface ChainingContext {
chainingFilters?: Filter[] | undefined;

View file

@ -6,11 +6,11 @@
* Side Public License, v 1.
*/
import { ParentIgnoreSettings } from '@kbn/controls-plugin/public';
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { PublishesUnifiedSearch, PublishingSubject } from '@kbn/presentation-publishing';
import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload';
import { BehaviorSubject, debounceTime, map, merge, Observable, switchMap } from 'rxjs';
import { ParentIgnoreSettings } from '../../..';
export interface ControlGroupFetchContext {
unifiedSearchFilters?: Filter[] | undefined;

View file

@ -26,10 +26,10 @@ import {
EuiTitle,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { ControlStyle, ParentIgnoreSettings } from '@kbn/controls-plugin/public';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import { ControlStyle, ParentIgnoreSettings } from '../..';
import { ControlStateManager } from '../types';
import { ControlStateManager } from '../controls/types';
import { ControlGroupStrings } from './control_group_strings';
import { ControlGroupApi, ControlGroupEditorState } from './types';

View file

@ -20,7 +20,7 @@ import {
import { combineLatest, map } from 'rxjs';
import { ControlsInOrder, getControlsInOrder } from './init_controls_manager';
import { ControlGroupRuntimeState, ControlPanelsState } from './types';
import { apiPublishesAsyncFilters } from '../data_controls/publishes_async_filters';
import { apiPublishesAsyncFilters } from '../controls/data_controls/publishes_async_filters';
export type ControlGroupComparatorState = Pick<
ControlGroupRuntimeState,

View file

@ -9,15 +9,6 @@
import React, { useEffect } from 'react';
import { BehaviorSubject } from 'rxjs';
import fastIsEqual from 'fast-deep-equal';
import {
ControlGroupChainingSystem,
ControlWidth,
CONTROL_GROUP_TYPE,
DEFAULT_CONTROL_GROW,
DEFAULT_CONTROL_STYLE,
DEFAULT_CONTROL_WIDTH,
} from '@kbn/controls-plugin/common';
import { ControlStyle, ParentIgnoreSettings } from '@kbn/controls-plugin/public';
import { CoreStart } from '@kbn/core/public';
import { DataView } from '@kbn/data-views-plugin/common';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
@ -32,6 +23,15 @@ import {
PublishesDataViews,
useBatchedPublishingSubjects,
} from '@kbn/presentation-publishing';
import { ControlStyle, ParentIgnoreSettings } from '../..';
import {
ControlGroupChainingSystem,
ControlWidth,
CONTROL_GROUP_TYPE,
DEFAULT_CONTROL_GROW,
DEFAULT_CONTROL_STYLE,
DEFAULT_CONTROL_WIDTH,
} from '../../../common';
import { chaining$, controlFetch$, controlGroupFetch$ } from './control_fetch';
import { initControlsManager } from './init_controls_manager';
import { openEditControlGroupFlyout } from './open_edit_control_group_flyout';
@ -40,6 +40,7 @@ import { ControlGroupApi, ControlGroupRuntimeState, ControlGroupSerializedState
import { ControlGroup } from './components/control_group';
import { initSelectionsManager } from './selections_manager';
import { initializeControlGroupUnsavedChanges } from './control_group_unsaved_changes_api';
import { openDataControlEditor } from '../controls/data_controls/open_data_control_editor';
export const getControlGroupEmbeddableFactory = (services: {
core: CoreStart;
@ -162,6 +163,28 @@ export const getControlGroupEmbeddableFactory = (services: {
i18n.translate('controls.controlGroup.displayName', {
defaultMessage: 'Controls',
}),
openAddDataControlFlyout: (settings) => {
const { controlInputTransform } = settings ?? {
controlInputTransform: (state) => state,
};
openDataControlEditor({
initialState: {
grow: api.grow.getValue(),
width: api.width.getValue(),
},
onSave: ({ type: controlType, state: initialState }) => {
api.addNewPanel({
panelType: controlType,
initialState: controlInputTransform!(
initialState as Partial<ControlGroupSerializedState>,
controlType
),
});
},
controlGroupApi: api,
services,
});
},
serializeState: () => {
const { panelsJSON, references } = controlsManager.serializeControls();
return {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { DefaultControlApi } from '../types';
import { DefaultControlApi } from '../controls/types';
import { initControlsManager } from './init_controls_manager';
jest.mock('uuid', () => ({

View file

@ -19,7 +19,7 @@ import { PublishingSubject, StateComparators } from '@kbn/presentation-publishin
import { omit } from 'lodash';
import { apiHasSnapshottableState } from '@kbn/presentation-containers/interfaces/serialized_state';
import { ControlPanelsState, ControlPanelState } from './types';
import { DefaultControlApi, DefaultControlState } from '../types';
import { DefaultControlApi, DefaultControlState } from '../controls/types';
import { ControlGroupComparatorState } from './control_group_unsaved_changes_api';
export type ControlsInOrder = Array<{ id: string; type: string }>;

View file

@ -15,7 +15,7 @@ import { toMountPoint } from '@kbn/react-kibana-mount';
import React from 'react';
import { BehaviorSubject } from 'rxjs';
import { ControlStateManager } from '../types';
import { ControlStateManager } from '../controls/types';
import { ControlGroupEditor } from './control_group_editor';
import { ControlGroupApi, ControlGroupEditorState } from './types';

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { CoreSetup } from '@kbn/core/public';
import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public';
import type { ControlsPluginStartDeps } from '../../types';
import { CONTROL_GROUP_TYPE } from '../../../common';
export function registerControlGroupEmbeddable(
coreSetup: CoreSetup<ControlsPluginStartDeps>,
embeddableSetup: EmbeddableSetup
) {
embeddableSetup.registerReactEmbeddableFactory(CONTROL_GROUP_TYPE, async () => {
const [{ getControlGroupEmbeddableFactory }, [coreStart, depsStart]] = await Promise.all([
import('./get_control_group_factory'),
coreSetup.getStartServices(),
]);
return getControlGroupEmbeddableFactory({
core: coreStart,
dataViews: depsStart.data.dataViews,
});
});
}

View file

@ -6,9 +6,9 @@
* Side Public License, v 1.
*/
import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '@kbn/controls-plugin/common';
import { SerializedPanelState } from '@kbn/presentation-containers';
import { omit } from 'lodash';
import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../common';
import { ControlGroupRuntimeState, ControlGroupSerializedState } from './types';
export const deserializeControlGroup = (

View file

@ -6,9 +6,8 @@
* Side Public License, v 1.
*/
import { ControlGroupChainingSystem } from '@kbn/controls-plugin/common/control_group/types';
import { ParentIgnoreSettings } from '@kbn/controls-plugin/public';
import { ControlStyle, ControlWidth } from '@kbn/controls-plugin/public/types';
import { Observable } from 'rxjs';
import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public';
import { Filter } from '@kbn/es-query';
import {
@ -27,8 +26,12 @@ import {
PublishingSubject,
} from '@kbn/presentation-publishing';
import { PublishesDataViews } from '@kbn/presentation-publishing/interfaces/publishes_data_views';
import { Observable } from 'rxjs';
import { DefaultControlState, PublishesControlDisplaySettings } from '../types';
import { ParentIgnoreSettings } from '../..';
import { ControlInputTransform } from '../../../common';
import { ControlGroupChainingSystem } from '../../../common/control_group/types';
import { ControlStyle, ControlWidth } from '../../types';
import { DefaultControlState, PublishesControlDisplaySettings } from '../controls/types';
import { ControlFetchContext } from './control_fetch/control_fetch';
/** The control display settings published by the control group are the "default" */
@ -66,6 +69,9 @@ export type ControlGroupApi = PresentationContainer &
ignoreParentSettings$: PublishingSubject<ParentIgnoreSettings | undefined>;
allowExpensiveQueries$: PublishingSubject<boolean>;
untilInitialized: () => Promise<void>;
openAddDataControlFlyout: (settings?: {
controlInputTransform?: ControlInputTransform;
}) => void;
};
export interface ControlGroupRuntimeState {

View file

@ -6,8 +6,8 @@
* Side Public License, v 1.
*/
import { RANGE_SLIDER_CONTROL } from '@kbn/controls-plugin/common';
import { i18n } from '@kbn/i18n';
import { RANGE_SLIDER_CONTROL } from '../../../../common';
export const DataControlEditorStrings = {
manageControl: {

View file

@ -16,14 +16,14 @@ import { TimeRange } from '@kbn/es-query';
import { I18nProvider } from '@kbn/i18n-react';
import { act, fireEvent, render, RenderResult, waitFor } from '@testing-library/react';
import { getAllControlTypes, getControlFactory } from '../control_factory_registry';
jest.mock('../control_factory_registry', () => ({
...jest.requireActual('../control_factory_registry'),
import { getAllControlTypes, getControlFactory } from '../../control_factory_registry';
jest.mock('../../control_factory_registry', () => ({
...jest.requireActual('../../control_factory_registry'),
getAllControlTypes: jest.fn(),
getControlFactory: jest.fn(),
}));
import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '@kbn/controls-plugin/common';
import { ControlGroupApi } from '../control_group/types';
import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../../common';
import { ControlGroupApi } from '../../control_group/types';
import { DataControlEditor } from './data_control_editor';
import { DataControlEditorState } from './open_data_control_editor';
import {

View file

@ -30,13 +30,6 @@ import {
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
import {
ControlWidth,
DEFAULT_CONTROL_GROW,
DEFAULT_CONTROL_WIDTH,
} from '@kbn/controls-plugin/common';
import { CONTROL_WIDTH_OPTIONS } from '@kbn/controls-plugin/public';
import { DataControlFieldRegistry } from '@kbn/controls-plugin/public/types';
import { DataViewField } from '@kbn/data-views-plugin/common';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
@ -45,13 +38,16 @@ import {
LazyFieldPicker,
withSuspense,
} from '@kbn/presentation-util-plugin/public';
import { DataControlFieldRegistry } from '../../../types';
import { CONTROL_WIDTH_OPTIONS } from '../../..';
import { ControlWidth, DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../../common';
import { getAllControlTypes, getControlFactory } from '../control_factory_registry';
import { ControlGroupApi } from '../control_group/types';
import { getAllControlTypes, getControlFactory } from '../../control_factory_registry';
import { ControlGroupApi } from '../../control_group/types';
import { DataControlEditorStrings } from './data_control_constants';
import { getDataControlFieldRegistry } from './data_control_editor_utils';
import { DataControlEditorState } from './open_data_control_editor';
import { DataControlFactory, isDataControlFactory } from './types';
import { DataControlFactory, DefaultDataControlState, isDataControlFactory } from './types';
export interface ControlEditorProps<State extends DataControlEditorState = DataControlEditorState> {
initialState: State;
@ -213,7 +209,7 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon
data-test-subj="control-editor-custom-settings"
>
<CustomSettings
initialState={initialState}
initialState={initialState as DefaultDataControlState}
field={fieldRegistry[editorState.fieldName].field}
updateState={(newState) => setEditorState({ ...editorState, ...newState })}
setControlEditorValid={setControlOptionsValid}

View file

@ -8,9 +8,9 @@
import { memoize } from 'lodash';
import { DataControlFieldRegistry } from '@kbn/controls-plugin/public/types';
import { DataView } from '@kbn/data-views-plugin/common';
import { getAllControlTypes, getControlFactory } from '../control_factory_registry';
import { DataControlFieldRegistry } from '../../../types';
import { getAllControlTypes, getControlFactory } from '../../control_factory_registry';
import { isDataControlFactory } from './types';
/** TODO: This funciton is duplicated from the controls plugin to avoid exporting it */

View file

@ -10,7 +10,7 @@ import { coreMock } from '@kbn/core/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import type { DataView } from '@kbn/data-views-plugin/public';
import { first, skip } from 'rxjs';
import { ControlGroupApi } from '../control_group/types';
import { ControlGroupApi } from '../../control_group/types';
import { initializeDataControl } from './initialize_data_control';
describe('initializeDataControl', () => {

View file

@ -21,7 +21,7 @@ import { SerializedPanelState } from '@kbn/presentation-containers';
import { StateComparators } from '@kbn/presentation-publishing';
import { i18n } from '@kbn/i18n';
import { ControlGroupApi } from '../control_group/types';
import { ControlGroupApi } from '../../control_group/types';
import { initializeDefaultControlApi } from '../initialize_default_control_api';
import { ControlApiInitialization, ControlStateManager, DefaultControlState } from '../types';
import { openDataControlEditor } from './open_data_control_editor';
@ -116,7 +116,7 @@ export const initializeDataControl = <EditorState extends object = {}>(
if (!field) {
defaultControl.api.setBlockingError(
new Error(
i18n.translate('controlsExamples.errors.fieldNotFound', {
i18n.translate('controls.dataControl.fieldNotFound', {
defaultMessage: 'Could not locate field: {fieldName}',
values: { fieldName: nextFieldName },
})

View file

@ -8,13 +8,13 @@
import { BehaviorSubject } from 'rxjs';
import { OptionsListSuggestions } from '@kbn/controls-plugin/common/options_list/types';
import { DataViewField } from '@kbn/data-views-plugin/common';
import { PublishingSubject } from '@kbn/presentation-publishing';
import { OptionsListSelection } from '../../../../common/options_list/options_list_selections';
import { OptionsListSearchTechnique } from '../../../../common/options_list/suggestions_searching';
import { OptionsListSortingType } from '../../../../common/options_list/suggestions_sorting';
import { OptionsListSuggestions } from '../../../../../common/options_list/types';
import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections';
import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching';
import { OptionsListSortingType } from '../../../../../common/options_list/suggestions_sorting';
import { OptionsListDisplaySettings } from '../options_list_control/types';
export const getOptionsListMocks = () => {

View file

@ -16,11 +16,11 @@ import { tracksOverlays } from '@kbn/presentation-containers';
import { apiHasParentApi } from '@kbn/presentation-publishing';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { ControlGroupApi } from '../control_group/types';
import { ControlGroupApi } from '../../control_group/types';
import { DataControlEditor } from './data_control_editor';
import { DefaultDataControlState } from './types';
export type DataControlEditorState = Omit<DefaultDataControlState, 'fieldName'> & {
export type DataControlEditorState = Partial<DefaultDataControlState> & {
fieldName?: string;
controlType?: string;
controlId?: string;

View file

@ -24,8 +24,8 @@ import {
useBatchedPublishingSubjects,
} from '@kbn/presentation-publishing';
import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections';
import { MIN_POPOVER_WIDTH } from '../constants';
import { OptionsListSelection } from '../../../../../../common/options_list/options_list_selections';
import { MIN_POPOVER_WIDTH } from '../../../constants';
import { useOptionsListContext } from '../options_list_context_provider';
import { OptionsListPopover } from './options_list_popover';
import { OptionsListStrings } from '../options_list_strings';

View file

@ -16,7 +16,7 @@ import { getMockedControlGroupApi } from '../../../mocks/control_mocks';
import { CustomOptionsComponentProps, DefaultDataControlState } from '../../types';
import { OptionsListControlState } from '../types';
import { OptionsListEditorOptions } from './options_list_editor_options';
import { ControlGroupApi } from '../../../control_group/types';
import { ControlGroupApi } from '../../../../control_group/types';
import { BehaviorSubject } from 'rxjs';
describe('Options list sorting button', () => {

View file

@ -14,8 +14,8 @@ import { useStateFromPublishingSubject } from '@kbn/presentation-publishing';
import {
getCompatibleSearchTechniques,
OptionsListSearchTechnique,
} from '../../../../../common/options_list/suggestions_searching';
import { ControlSettingTooltipLabel } from '../../../components/control_setting_tooltip_label';
} from '../../../../../../common/options_list/suggestions_searching';
import { ControlSettingTooltipLabel } from '../../../../control_group/components/control_setting_tooltip_label';
import { CustomOptionsComponentProps } from '../../types';
import { DEFAULT_SEARCH_TECHNIQUE } from '../constants';
import { OptionsListControlState } from '../types';

View file

@ -19,7 +19,7 @@ import {
} from '@elastic/eui';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import { getCompatibleSearchTechniques } from '../../../../../common/options_list/suggestions_searching';
import { getCompatibleSearchTechniques } from '../../../../../../common/options_list/suggestions_searching';
import { useOptionsListContext } from '../options_list_context_provider';
import { OptionsListPopoverSortingButton } from './options_list_popover_sorting_button';
import { OptionsListStrings } from '../options_list_strings';

View file

@ -27,7 +27,7 @@ import {
getCompatibleSortingTypes,
OptionsListSortBy,
OPTIONS_LIST_DEFAULT_SORT,
} from '../../../../../common/options_list/suggestions_sorting';
} from '../../../../../../common/options_list/suggestions_sorting';
import { useOptionsListContext } from '../options_list_context_provider';
import { OptionsListStrings } from '../options_list_strings';

View file

@ -10,11 +10,11 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { EuiHighlight, EuiSelectable } from '@elastic/eui';
import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option';
import { OptionsListSuggestions } from '@kbn/controls-plugin/common/options_list/types';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import { euiThemeVars } from '@kbn/ui-theme';
import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections';
import { OptionsListSuggestions } from '../../../../../../common/options_list/types';
import { OptionsListSelection } from '../../../../../../common/options_list/options_list_selections';
import { MAX_OPTIONS_LIST_REQUEST_SIZE } from '../constants';
import { useOptionsListContext } from '../options_list_context_provider';
import { OptionsListStrings } from '../options_list_strings';

View file

@ -6,18 +6,14 @@
* Side Public License, v 1.
*/
import { OPTIONS_LIST_CONTROL } from '@kbn/controls-plugin/common';
import { OptionsListSearchTechnique } from '@kbn/controls-plugin/common/options_list/suggestions_searching';
import { OptionsListSortingType } from '@kbn/controls-plugin/common/options_list/suggestions_sorting';
import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching';
import { OptionsListSortingType } from '../../../../../common/options_list/suggestions_sorting';
export const OPTIONS_LIST_CONTROL_TYPE = OPTIONS_LIST_CONTROL;
export const DEFAULT_SEARCH_TECHNIQUE: OptionsListSearchTechnique = 'prefix';
export const OPTIONS_LIST_DEFAULT_SORT: OptionsListSortingType = {
by: '_count',
direction: 'desc',
};
export const MIN_POPOVER_WIDTH = 300;
export const MIN_OPTIONS_LIST_REQUEST_SIZE = 10;
export const MAX_OPTIONS_LIST_REQUEST_SIZE = 1000;

View file

@ -16,12 +16,11 @@ import {
withLatestFrom,
} from 'rxjs';
import { OptionsListSuccessResponse } from '@kbn/controls-plugin/common/options_list/types';
import { PublishingSubject } from '@kbn/presentation-publishing';
import { isValidSearch } from '../../../../common/options_list/suggestions_searching';
import { OptionsListSelection } from '../../../../common/options_list/options_list_selections';
import { ControlFetchContext } from '../../control_group/control_fetch';
import { OptionsListSuccessResponse } from '../../../../../common/options_list/types';
import { isValidSearch } from '../../../../../common/options_list/is_valid_search';
import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections';
import { ControlFetchContext } from '../../../control_group/control_fetch';
import { ControlStateManager } from '../../types';
import { DataControlServices } from '../types';
import { OptionsListFetchCache } from './options_list_fetch_cache';

View file

@ -9,20 +9,21 @@
import React, { useEffect } from 'react';
import { BehaviorSubject, combineLatest, debounceTime, filter, skip } from 'rxjs';
import { OptionsListSearchTechnique } from '@kbn/controls-plugin/common/options_list/suggestions_searching';
import { OptionsListSortingType } from '@kbn/controls-plugin/common/options_list/suggestions_sorting';
import {
OptionsListSuccessResponse,
OptionsListSuggestions,
} from '@kbn/controls-plugin/common/options_list/types';
import { buildExistsFilter, buildPhraseFilter, buildPhrasesFilter, Filter } from '@kbn/es-query';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import {
OPTIONS_LIST_CONTROL,
OptionsListSuccessResponse,
OptionsListSuggestions,
} from '../../../../../common/options_list/types';
import {
getSelectionAsFieldType,
OptionsListSelection,
} from '../../../../common/options_list/options_list_selections';
import { isValidSearch } from '../../../../common/options_list/suggestions_searching';
} from '../../../../../common/options_list/options_list_selections';
import { isValidSearch } from '../../../../../common/options_list/is_valid_search';
import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching';
import { OptionsListSortingType } from '../../../../../common/options_list/suggestions_sorting';
import { initializeDataControl } from '../initialize_data_control';
import { DataControlFactory, DataControlServices } from '../types';
import { OptionsListControl } from './components/options_list_control';
@ -30,7 +31,6 @@ import { OptionsListEditorOptions } from './components/options_list_editor_optio
import {
DEFAULT_SEARCH_TECHNIQUE,
MIN_OPTIONS_LIST_REQUEST_SIZE,
OPTIONS_LIST_CONTROL_TYPE,
OPTIONS_LIST_DEFAULT_SORT,
} from './constants';
import { fetchAndValidate$ } from './fetch_and_validate';
@ -43,7 +43,7 @@ export const getOptionsListControlFactory = (
services: DataControlServices
): DataControlFactory<OptionsListControlState, OptionsListControlApi> => {
return {
type: OPTIONS_LIST_CONTROL_TYPE,
type: OPTIONS_LIST_CONTROL,
order: 3, // should always be first, since this is the most popular control
getIconType: () => 'editorChecklist',
getDisplayName: OptionsListStrings.control.getDisplayName,
@ -86,7 +86,7 @@ export const getOptionsListControlFactory = (
Pick<OptionsListControlState, 'searchTechnique' | 'singleSelect'>
>(
uuid,
OPTIONS_LIST_CONTROL_TYPE,
OPTIONS_LIST_CONTROL,
initialState,
{ searchTechnique: searchTechnique$, singleSelect: singleSelect$ },
controlGroupApi,

View file

@ -15,7 +15,7 @@ import {
OptionsListComponentState,
OptionsListDisplaySettings,
} from './types';
import { OptionsListSelection } from '../../../../common/options_list/options_list_selections';
import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections';
export type ContextStateManager = ControlStateManager<
Omit<OptionsListComponentState, 'exclude' | 'existsSelected' | 'selectedOptions'>

View file

@ -10,7 +10,7 @@ import { BehaviorSubject } from 'rxjs';
import deepEqual from 'react-fast-compare';
import { PublishingSubject, StateComparators } from '@kbn/presentation-publishing';
import { OptionsListControlState } from './types';
import { OptionsListSelection } from '../../../../common/options_list/options_list_selections';
import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections';
export function initializeOptionsListSelections(
initialState: OptionsListControlState,

View file

@ -11,14 +11,14 @@ import hash from 'object-hash';
import dateMath from '@kbn/datemath';
import { getEsQueryConfig } from '@kbn/data-plugin/public';
import { buildEsQuery } from '@kbn/es-query';
import {
type OptionsListFailureResponse,
type OptionsListRequest,
type OptionsListResponse,
type OptionsListSuccessResponse,
} from '@kbn/controls-plugin/common/options_list/types';
import { getEsQueryConfig } from '@kbn/data-plugin/public';
import { buildEsQuery } from '@kbn/es-query';
} from '../../../../../common/options_list/types';
import { DataControlServices } from '../types';
const REQUEST_CACHE_SIZE = 50; // only store a max of 50 responses

View file

@ -7,7 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { OptionsListSearchTechnique } from '../../../../common/options_list/suggestions_searching';
import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching';
export const OptionsListStrings = {
control: {

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { CoreSetup } from '@kbn/core/public';
import type { ControlsPluginStartDeps } from '../../../../types';
import { registerControlFactory } from '../../../control_factory_registry';
import { OPTIONS_LIST_CONTROL } from '../../../../../common';
export function registerOptionsListControl(coreSetup: CoreSetup<ControlsPluginStartDeps>) {
registerControlFactory(OPTIONS_LIST_CONTROL, async () => {
const [{ getOptionsListControlFactory }, [coreStart, depsStart]] = await Promise.all([
import('./get_options_list_control_factory'),
coreSetup.getStartServices(),
]);
return getOptionsListControlFactory({
core: coreStart,
data: depsStart.data,
dataViews: depsStart.data.dataViews,
});
});
}

View file

@ -8,12 +8,12 @@
import { BehaviorSubject } from 'rxjs';
import { OptionsListSearchTechnique } from '@kbn/controls-plugin/common/options_list/suggestions_searching';
import { OptionsListSortingType } from '@kbn/controls-plugin/common/options_list/suggestions_sorting';
import { OptionsListSuggestions } from '@kbn/controls-plugin/common/options_list/types';
import { PublishingSubject } from '@kbn/presentation-publishing';
import { OptionsListSelection } from '../../../../common/options_list/options_list_selections';
import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching';
import { OptionsListSortingType } from '../../../../../common/options_list/suggestions_sorting';
import { OptionsListSuggestions } from '../../../../../common/options_list/types';
import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections';
import { DataControlApi, DefaultDataControlState } from '../types';
export interface OptionsListDisplaySettings {

View file

@ -17,14 +17,15 @@ import { RangeSliderControl } from './components/range_slider_control';
import { hasNoResults$ } from './has_no_results';
import { minMax$ } from './min_max';
import { RangeSliderStrings } from './range_slider_strings';
import { RangesliderControlApi, RangesliderControlState, RANGE_SLIDER_CONTROL_TYPE } from './types';
import { RangesliderControlApi, RangesliderControlState } from './types';
import { initializeRangeControlSelections } from './range_control_selections';
import { RANGE_SLIDER_CONTROL } from '../../../../../common';
export const getRangesliderControlFactory = (
services: DataControlServices
): DataControlFactory<RangesliderControlState, RangesliderControlApi> => {
return {
type: RANGE_SLIDER_CONTROL_TYPE,
type: RANGE_SLIDER_CONTROL,
getIconType: () => 'controlsHorizontal',
getDisplayName: RangeSliderStrings.control.getDisplayName,
isFieldCompatible: (field) => {
@ -61,7 +62,7 @@ export const getRangesliderControlFactory = (
const dataControl = initializeDataControl<Pick<RangesliderControlState, 'step'>>(
uuid,
RANGE_SLIDER_CONTROL_TYPE,
RANGE_SLIDER_CONTROL,
initialState,
{
step: step$,

View file

@ -12,8 +12,8 @@ import { DataView } from '@kbn/data-views-plugin/public';
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { PublishesDataViews } from '@kbn/presentation-publishing';
import { combineLatest, lastValueFrom, Observable, switchMap, tap } from 'rxjs';
import { ControlFetchContext } from '../../control_group/control_fetch';
import { ControlGroupApi } from '../../control_group/types';
import { ControlFetchContext } from '../../../control_group/control_fetch';
import { ControlGroupApi } from '../../../control_group/types';
import { DataControlApi } from '../types';
export function hasNoResults$({

View file

@ -12,7 +12,7 @@ import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { PublishesDataViews, PublishingSubject } from '@kbn/presentation-publishing';
import { combineLatest, lastValueFrom, Observable, switchMap, tap } from 'rxjs';
import { ControlFetchContext } from '../../control_group/control_fetch';
import { ControlFetchContext } from '../../../control_group/control_fetch';
export function minMax$({
controlFetch$,

View file

@ -11,23 +11,23 @@ import { i18n } from '@kbn/i18n';
export const RangeSliderStrings = {
control: {
getDisplayName: () =>
i18n.translate('controlsExamples.rangeSliderControl.displayName', {
i18n.translate('controls.rangeSliderControl.displayName', {
defaultMessage: 'Range slider',
}),
getInvalidSelectionWarningLabel: () =>
i18n.translate('controlsExamples.rangeSlider.control.invalidSelectionWarningLabel', {
i18n.translate('controls.rangeSlider.control.invalidSelectionWarningLabel', {
defaultMessage: 'Selected range returns no results.',
}),
},
editor: {
getStepTitle: () =>
i18n.translate('controlsExamples.rangeSlider.editor.stepSizeTitle', {
i18n.translate('controls.rangeSlider.editor.stepSizeTitle', {
defaultMessage: 'Step size',
}),
},
popover: {
getNoAvailableDataHelpText: () =>
i18n.translate('controlsExamples.rangeSlider.popover.noAvailableDataHelpText', {
i18n.translate('controls.rangeSlider.popover.noAvailableDataHelpText', {
defaultMessage: 'There is no data to display. Adjust the time range and filters.',
}),
},

View file

@ -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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { CoreSetup } from '@kbn/core/public';
import type { ControlsPluginStartDeps } from '../../../../types';
import { registerControlFactory } from '../../../control_factory_registry';
import { RANGE_SLIDER_CONTROL } from '../../../../../common';
export function registerRangeSliderControl(coreSetup: CoreSetup<ControlsPluginStartDeps>) {
registerControlFactory(RANGE_SLIDER_CONTROL, async () => {
const [{ getRangesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([
import('./get_range_slider_control_factory'),
coreSetup.getStartServices(),
]);
return getRangesliderControlFactory({
core: coreStart,
data: depsStart.data,
dataViews: depsStart.data.dataViews,
});
});
}

View file

@ -8,8 +8,6 @@
import { DataControlApi, DefaultDataControlState } from '../types';
export const RANGE_SLIDER_CONTROL_TYPE = 'rangeSlider';
export type RangeValue = [string, string];
export interface RangesliderControlState extends DefaultDataControlState {

View file

@ -17,7 +17,7 @@ import {
PublishesPanelTitle,
PublishingSubject,
} from '@kbn/presentation-publishing';
import { ControlGroupApi } from '../control_group/types';
import { ControlGroupApi } from '../../control_group/types';
import { ControlFactory, DefaultControlApi, DefaultControlState } from '../types';
import { PublishesAsyncFilters } from './publishes_async_filters';

View file

@ -8,9 +8,9 @@
import { BehaviorSubject } from 'rxjs';
import { ControlWidth } from '@kbn/controls-plugin/common';
import { SerializedPanelState } from '@kbn/presentation-containers';
import { StateComparators } from '@kbn/presentation-publishing';
import { ControlWidth } from '../../../common';
import {
ControlApiInitialization,

View file

@ -9,8 +9,8 @@
import { TimeRange } from '@kbn/es-query';
import { PublishesUnifiedSearch, StateComparators } from '@kbn/presentation-publishing';
import { BehaviorSubject } from 'rxjs';
import { ControlFetchContext } from '../control_group/control_fetch/control_fetch';
import { ControlGroupApi } from '../control_group/types';
import { ControlFetchContext } from '../../control_group/control_fetch/control_fetch';
import { ControlGroupApi } from '../../control_group/types';
import { ControlApiRegistration, ControlFactory, DefaultControlApi } from '../types';
export const getMockedControlGroupApi = (

Some files were not shown because too many files have changed in this diff Show more