mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
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:
parent
60c5eca33e
commit
1616c3c22f
114 changed files with 378 additions and 1102 deletions
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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'];
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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() {}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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;
|
|
@ -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",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ pageLoadAssetSize:
|
|||
cloudSecurityPosture: 19109
|
||||
console: 46091
|
||||
contentManagement: 16254
|
||||
controls: 55082
|
||||
controls: 60000
|
||||
core: 435325
|
||||
crossClusterReplication: 65408
|
||||
customIntegrations: 22034
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 },
|
||||
})
|
|
@ -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
|
|
@ -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 {
|
|
@ -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';
|
|
@ -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
|
|
@ -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';
|
|
@ -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;
|
|
@ -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;
|
|
@ -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';
|
||||
|
|
@ -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,
|
|
@ -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 {
|
|
@ -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', () => ({
|
|
@ -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 }>;
|
|
@ -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';
|
||||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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 = (
|
|
@ -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 {
|
|
@ -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: {
|
|
@ -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 {
|
|
@ -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}
|
|
@ -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 */
|
|
@ -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', () => {
|
|
@ -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 },
|
||||
})
|
|
@ -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 = () => {
|
|
@ -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;
|
|
@ -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';
|
|
@ -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', () => {
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
||||
|
|
@ -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';
|
|
@ -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;
|
|
@ -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';
|
|
@ -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,
|
|
@ -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'>
|
|
@ -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,
|
|
@ -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
|
|
@ -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: {
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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 {
|
|
@ -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$,
|
|
@ -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$({
|
|
@ -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$,
|
|
@ -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.',
|
||||
}),
|
||||
},
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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 {
|
|
@ -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';
|
||||
|
|
@ -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,
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue