From 7580c12d07f19a2b1b17d46e4abf3ab07da3629c Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Thu, 6 Jan 2022 16:55:41 +0100 Subject: [PATCH 1/6] fixing broken links (#122415) --- docs/developer/best-practices/index.asciidoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/developer/best-practices/index.asciidoc b/docs/developer/best-practices/index.asciidoc index 04422a613475a..c3f8239e9af91 100644 --- a/docs/developer/best-practices/index.asciidoc +++ b/docs/developer/best-practices/index.asciidoc @@ -73,16 +73,16 @@ services you should consider: * {kib-repo}tree/{branch}/src/plugins/data/README.md[Data services] -** {kib-repo}tree/{branch}/src/plugins/data/public/search/README.md[Search +** {kib-repo}tree/{branch}/src/plugins/data/README.mdx#search[Search strategies] *** Use the `esSearchStrategy` to make raw queries to ES that will support async searching and partial results, as well as injecting the right advanced settings like whether to include frozen indices or not. -* {kib-repo}tree/{branch}/src/plugins/embeddable/README.md[Embeddables] +* {kib-repo}tree/{branch}/src/plugins/embeddable/README.asciidoc[Embeddables] ** Rendering maps, visualizations, dashboards in your application ** Register new widgets that will can be added to a dashboard or Canvas workpad, or rendered in another plugin. -* {kib-repo}tree/{branch}/src/plugins/ui_actions/README.md[UiActions] +* {kib-repo}tree/{branch}/src/plugins/ui_actions/README.asciidoc[UiActions] ** Let other plugins inject functionality into your application ** Inject custom functionality into other plugins * Stateless helper utilities From 302566e441e73f8ce37447786cf18ec9caf12b6b Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Thu, 6 Jan 2022 09:16:27 -0700 Subject: [PATCH 2/6] [Metrics UI] Honor time unit for Inventory Threshold (#122294) * [Metrics UI] Honor time unit for Inventory Threshold * Adding tests * fixing the threshold on the tests * Update x-pack/test/api_integration/apis/metrics_ui/constants.ts Co-authored-by: Claudio Procida * fixing double quotes * moving the conversion into the code Co-authored-by: Claudio Procida --- .../evaluate_condition.ts | 11 +- .../apis/metrics_ui/constants.ts | 4 + .../api_integration/apis/metrics_ui/index.js | 1 + .../metrics_ui/inventory_threshold_alert.ts | 128 + .../infra/8.0.0/hosts_only/data.json.gz | Bin 0 -> 345 bytes .../infra/8.0.0/hosts_only/mappings.json | 15338 ++++++++++++++++ 6 files changed, 15479 insertions(+), 3 deletions(-) create mode 100644 x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts create mode 100644 x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/data.json.gz create mode 100644 x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/mappings.json diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts index 364c6b5a0d23a..b78c5eb291adb 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts @@ -42,6 +42,7 @@ export const evaluateCondition = async ({ compositeSize, filterQuery, lookbackSize, + startTime, }: { condition: InventoryMetricConditions; nodeType: InventoryItemType; @@ -51,14 +52,18 @@ export const evaluateCondition = async ({ compositeSize: number; filterQuery?: string; lookbackSize?: number; + startTime?: number; }): Promise> => { const { comparator, warningComparator, metric, customMetric } = condition; let { threshold, warningThreshold } = condition; + const to = startTime ? moment(startTime) : moment(); + const timerange = { - to: Date.now(), - from: moment().subtract(condition.timeSize, condition.timeUnit).toDate().getTime(), - interval: condition.timeUnit, + to: to.valueOf(), + from: to.clone().subtract(condition.timeSize, condition.timeUnit).valueOf(), + interval: `${condition.timeSize}${condition.timeUnit}`, + forceInterval: true, } as InfraTimerangeInput; if (lookbackSize) { timerange.lookbackSize = lookbackSize; diff --git a/x-pack/test/api_integration/apis/metrics_ui/constants.ts b/x-pack/test/api_integration/apis/metrics_ui/constants.ts index 90db71ae08130..57963179aa8e4 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/constants.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/constants.ts @@ -19,6 +19,10 @@ export const DATES = { }, }, '8.0.0': { + hosts_only: { + min: new Date('2022-01-02T00:00:00.000Z').getTime(), + max: new Date('2022-01-02T00:05:30.000Z').getTime(), + }, logs_and_metrics: { min: 1562786660845, max: 1562786716965, diff --git a/x-pack/test/api_integration/apis/metrics_ui/index.js b/x-pack/test/api_integration/apis/metrics_ui/index.js index 72c79faaa4372..77560d966350e 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/index.js +++ b/x-pack/test/api_integration/apis/metrics_ui/index.js @@ -23,5 +23,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./metrics_process_list')); loadTestFile(require.resolve('./metrics_process_list_chart')); loadTestFile(require.resolve('./infra_log_analysis_validation_log_entry_datasets')); + loadTestFile(require.resolve('./inventory_threshold_alert')); }); } diff --git a/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts b/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts new file mode 100644 index 0000000000000..a6e0ce1bc628f --- /dev/null +++ b/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts @@ -0,0 +1,128 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { convertToKibanaClient } from '@kbn/test'; +import { + Comparator, + InventoryMetricConditions, +} from '../../../../plugins/infra/server/lib/alerting/inventory_metric_threshold/types'; +import { InfraSource } from '../../../../plugins/infra/server/lib/sources'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { DATES } from './constants'; +import { evaluateCondition } from '../../../../plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition'; +import { InventoryItemType } from '../../../../plugins/infra/common/inventory_models/types'; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const esClient = getService('es'); + + const baseCondition: InventoryMetricConditions = { + metric: 'cpu', + timeSize: 1, + timeUnit: 'm', + sourceId: 'default', + threshold: [100], + comparator: Comparator.GT, + }; + + const source: InfraSource = { + id: 'default', + origin: 'internal', + configuration: { + name: 'Default', + description: '', + logIndices: { + type: 'index_pattern', + indexPatternId: 'some-test-id', + }, + metricAlias: 'metricbeat-*', + inventoryDefaultView: 'default', + metricsExplorerDefaultView: 'default', + anomalyThreshold: 70, + fields: { + message: ['message'], + }, + logColumns: [ + { + timestampColumn: { + id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f', + }, + }, + { + fieldColumn: { + id: ' eb9777a8-fcd3-420e-ba7d-172fff6da7a2', + field: 'event.dataset', + }, + }, + { + messageColumn: { + id: 'b645d6da-824b-4723-9a2a-e8cece1645c0', + }, + }, + ], + }, + }; + + const baseOptions = { + condition: baseCondition, + nodeType: 'host' as InventoryItemType, + source, + logQueryFields: void 0, + compositeSize: 10000, + startTime: DATES['8.0.0'].hosts_only.max, + }; + + describe('Inventory Threshold Rule Executor', () => { + before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/hosts_only')); + after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/8.0.0/hosts_only')); + it('should work FOR LAST 1 minute', async () => { + const results = await evaluateCondition({ + ...baseOptions, + esClient: convertToKibanaClient(esClient), + }); + expect(results).to.eql({ + 'host-01': { + metric: 'cpu', + timeSize: 1, + timeUnit: 'm', + sourceId: 'default', + threshold: [100], + comparator: '>', + shouldFire: [true], + shouldWarn: [false], + isNoData: [false], + isError: false, + currentValue: 1.01, + }, + }); + }); + it('should work FOR LAST 5 minute', async () => { + const options = { + ...baseOptions, + condition: { ...baseCondition, timeSize: 5 }, + esClient: convertToKibanaClient(esClient), + }; + const results = await evaluateCondition(options); + expect(results).to.eql({ + 'host-01': { + metric: 'cpu', + timeSize: 5, + timeUnit: 'm', + sourceId: 'default', + threshold: [100], + comparator: '>', + shouldFire: [false], + shouldWarn: [false], + isNoData: [false], + isError: false, + currentValue: 0.24000000000000002, + }, + }); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/data.json.gz b/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..bdd31f088a41ab7c0f81423cdedb4a5d508af7a6 GIT binary patch literal 345 zcmV-f0jB;RiwFp`-PBQOZ*BnXn9pj2Fc8M?K857GE|a*nT~B?8J@ry- z9H>xNF|q6-zI!vrD4hV$`6Y(rV~Hm^sZn>8e8EB|0)*HCH*1QTia$@)ji#Euliv-D@~EpCl`iU* zVyTL0m4G+^{!%j4aKY=bygbd zd0pA|Gl8V0LuEw1vDIE-cN0n1lGN7Bz%6~8CH~O$m(=L?>+?;|F&d+2_&Zjy0M;~M zb&WOkj+FK^O;0$77vonw9Qj&&E@ r>K^NU0j$BT&arav&NW+yUH=Q!IZ^*V4zqPQ)d$fJM Date: Thu, 6 Jan 2022 17:18:38 +0100 Subject: [PATCH 3/6] [Uptime][Monitor Management]Only show minutes as monitor interval plus do not show Zip Url browser monitor source type (for tech preview). (#122407) https://github.com/elastic/uptime/issues/427 https://github.com/elastic/uptime/issues/428 --- .../browser/source_field.test.tsx | 27 +++++-- .../fleet_package/browser/source_field.tsx | 20 ++++-- .../contexts/policy_config_context.tsx | 16 ++++- .../fleet_package/schedule_field.test.tsx | 72 ++++++++++++++++--- .../fleet_package/schedule_field.tsx | 25 +++++-- .../edit_monitor_config.tsx | 10 ++- .../pages/monitor_management/add_monitor.tsx | 8 ++- 7 files changed, 148 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.test.tsx index 402bd175a09ea..3d1d50abb487f 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.test.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.test.tsx @@ -9,8 +9,9 @@ import 'jest-canvas-mock'; import React from 'react'; import { fireEvent, screen, waitFor } from '@testing-library/react'; import { render } from '../../../lib/helper/rtl_helpers'; +import { IPolicyConfigContextProvider } from '../contexts/policy_config_context'; import { SourceField, defaultValues } from './source_field'; -import { BrowserSimpleFieldsContextProvider } from '../contexts'; +import { BrowserSimpleFieldsContextProvider, PolicyConfigContextProvider } from '../contexts'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ ...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'), @@ -43,11 +44,15 @@ jest.mock('../../../../../../../src/plugins/kibana_react/public', () => { const onChange = jest.fn(); describe('', () => { - const WrappedComponent = () => { + const WrappedComponent = ({ + isZipUrlSourceEnabled, + }: Omit) => { return ( - - - + + + + + ); }; @@ -66,4 +71,16 @@ describe('', () => { expect(onChange).toBeCalledWith({ ...defaultValues, zipUrl }); }); }); + + it('shows ZipUrl source type by default', async () => { + render(); + + expect(screen.getByTestId('syntheticsSourceTab__zipUrl')).toBeInTheDocument(); + }); + + it('does not show ZipUrl source type when isZipUrlSourceEnabled = false', async () => { + render(); + + expect(screen.queryByTestId('syntheticsSourceTab__zipUrl')).not.toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.tsx b/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.tsx index a3de661a49a5b..3861537a72c11 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.tsx @@ -10,6 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiTabbedContent, + EuiTabbedContentTab, EuiFormRow, EuiFieldText, EuiFieldPassword, @@ -18,6 +19,7 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; +import { usePolicyConfigContext } from '../contexts'; import { OptionalLabel } from '../optional_label'; import { CodeEditor } from '../code_editor'; import { ScriptRecorderFields } from './script_recorder_fields'; @@ -59,18 +61,21 @@ export const defaultValues = { fileName: '', }; -const getDefaultTab = (defaultConfig: SourceConfig) => { +const getDefaultTab = (defaultConfig: SourceConfig, isZipUrlSourceEnabled = true) => { if (defaultConfig.inlineScript && defaultConfig.isGeneratedScript) { return SourceType.SCRIPT_RECORDER; } else if (defaultConfig.inlineScript) { return SourceType.INLINE; } - return SourceType.ZIP; + return isZipUrlSourceEnabled ? SourceType.ZIP : SourceType.INLINE; }; export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props) => { - const [sourceType, setSourceType] = useState(getDefaultTab(defaultConfig)); + const { isZipUrlSourceEnabled } = usePolicyConfigContext(); + const [sourceType, setSourceType] = useState( + getDefaultTab(defaultConfig, isZipUrlSourceEnabled) + ); const [config, setConfig] = useState(defaultConfig); useEffect(() => { @@ -84,9 +89,10 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props) /> ); - const tabs = [ + const zipUrlSourceTabId = 'syntheticsBrowserZipURLConfig'; + const allTabs = [ { - id: 'syntheticsBrowserZipURLConfig', + id: zipUrlSourceTabId, name: zipUrlLabel, 'data-test-subj': `syntheticsSourceTab__zipUrl`, content: ( @@ -329,6 +335,10 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props) }, ]; + const tabs = isZipUrlSourceEnabled + ? allTabs + : allTabs.filter((tab: EuiTabbedContentTab) => tab.id !== zipUrlSourceTabId); + return ( ({ defaultName = '', defaultLocations = [], isEditable = false, + isZipUrlSourceEnabled = true, + allowedScheduleUnits = [ScheduleUnit.MINUTES, ScheduleUnit.SECONDS], }: IPolicyConfigContextProvider) { const [monitorType, setMonitorType] = useState(defaultMonitorType); const [name, setName] = useState(defaultName); @@ -102,11 +110,14 @@ export function PolicyConfigContextProvider({ defaultLocations, locations, setLocations, - }; + isZipUrlSourceEnabled, + allowedScheduleUnits, + } as IPolicyConfigContext; }, [ monitorType, defaultMonitorType, isTLSEnabled, + isZipUrlSourceEnabled, isZipUrlTLSEnabled, defaultIsTLSEnabled, defaultIsZipUrlTLSEnabled, @@ -115,6 +126,7 @@ export function PolicyConfigContextProvider({ defaultName, locations, defaultLocations, + allowedScheduleUnits, ]); return ; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/schedule_field.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/schedule_field.test.tsx index 3358d1edabcc9..c2f99aaee2160 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/schedule_field.test.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/schedule_field.test.tsx @@ -5,31 +5,80 @@ * 2.0. */ +import { waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React, { useState } from 'react'; -import { fireEvent, waitFor } from '@testing-library/react'; import { render } from '../../lib/helper/rtl_helpers'; +import { PolicyConfigContextProvider } from './contexts'; +import { IPolicyConfigContextProvider } from './contexts/policy_config_context'; import { ScheduleField } from './schedule_field'; import { ScheduleUnit } from './types'; describe('', () => { const number = '1'; const unit = ScheduleUnit.MINUTES; - const WrappedComponent = () => { + const WrappedComponent = ({ + allowedScheduleUnits, + }: Omit) => { const [config, setConfig] = useState({ number, unit, }); return ( - setConfig(value)} - /> + + setConfig(value)} + /> + ); }; - it('hanles schedule', () => { + it('shows all options by default (allowedScheduleUnits is not provided)', () => { + const { getByText } = render(); + expect(getByText('Minutes')).toBeInTheDocument(); + expect(getByText('Seconds')).toBeInTheDocument(); + }); + + it('shows only Minutes when allowedScheduleUnits = [ScheduleUnit.Minutes])', () => { + const { queryByText } = render( + + ); + expect(queryByText('Minutes')).toBeInTheDocument(); + expect(queryByText('Seconds')).not.toBeInTheDocument(); + }); + + it('shows only Seconds when allowedScheduleUnits = [ScheduleUnit.Seconds])', () => { + const { queryByText } = render( + + ); + expect(queryByText('Minutes')).not.toBeInTheDocument(); + expect(queryByText('Seconds')).toBeInTheDocument(); + }); + + it('only accepts whole number when allowedScheduleUnits = [ScheduleUnit.Minutes])', async () => { + const { getByTestId } = render( + + ); + const input = getByTestId('scheduleFieldInput') as HTMLInputElement; + const select = getByTestId('scheduleFieldSelect') as HTMLInputElement; + expect(input.value).toBe(number); + expect(select.value).toBe(ScheduleUnit.MINUTES); + + userEvent.clear(input); + userEvent.type(input, '1.5'); + + // Click away to cause blur on input + userEvent.click(select); + + await waitFor(() => { + expect(input.value).toBe('2'); + }); + }); + + it('handles schedule', () => { const { getByText, getByTestId } = render(); const input = getByTestId('scheduleFieldInput') as HTMLInputElement; const select = getByTestId('scheduleFieldSelect') as HTMLInputElement; @@ -38,7 +87,7 @@ describe('', () => { expect(getByText('Minutes')).toBeInTheDocument(); }); - it('hanles on change', async () => { + it('handles on change', async () => { const { getByText, getByTestId } = render(); const input = getByTestId('scheduleFieldInput') as HTMLInputElement; const select = getByTestId('scheduleFieldSelect') as HTMLInputElement; @@ -47,13 +96,14 @@ describe('', () => { expect(input.value).toBe(number); expect(select.value).toBe(unit); - fireEvent.change(input, { target: { value: newNumber } }); + userEvent.clear(input); + userEvent.type(input, newNumber); await waitFor(() => { expect(input.value).toBe(newNumber); }); - fireEvent.change(select, { target: { value: newUnit } }); + userEvent.selectOptions(select, newUnit); await waitFor(() => { expect(select.value).toBe(newUnit); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/schedule_field.tsx b/x-pack/plugins/uptime/public/components/fleet_package/schedule_field.tsx index 267127c59e6dc..4042821834f3f 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/schedule_field.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/schedule_field.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; - import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { usePolicyConfigContext } from './contexts'; import { ConfigKey, MonitorFields, ScheduleUnit } from './types'; interface Props { @@ -18,6 +18,14 @@ interface Props { } export const ScheduleField = ({ number, onChange, unit }: Props) => { + const { allowedScheduleUnits } = usePolicyConfigContext(); + const options = !allowedScheduleUnits?.length + ? allOptions + : allOptions.filter((opt) => allowedScheduleUnits.includes(opt.value)); + + // When only minutes are allowed, don't allow user to input fractional value + const allowedStep = options.length === 1 && options[0].value === ScheduleUnit.MINUTES ? 1 : 'any'; + return ( @@ -30,13 +38,20 @@ export const ScheduleField = ({ number, onChange, unit }: Props) => { )} id="syntheticsFleetScheduleField--number" data-test-subj="scheduleFieldInput" - step={'any'} + step={allowedStep} min={1} value={number} onChange={(event) => { const updatedNumber = event.target.value; onChange({ number: updatedNumber, unit }); }} + onBlur={(event) => { + // Enforce whole number + if (allowedStep === 1) { + const updatedNumber = `${Math.ceil(+event.target.value)}`; + onChange({ number: updatedNumber, unit }); + } + }} /> @@ -61,7 +76,7 @@ export const ScheduleField = ({ number, onChange, unit }: Props) => { ); }; -const options = [ +const allOptions = [ { text: i18n.translate('xpack.uptime.createPackagePolicy.stepConfigure.scheduleField.seconds', { defaultMessage: 'Seconds', diff --git a/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx b/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx index fb9d2302b5b35..46932dd1fc5fd 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx @@ -6,7 +6,13 @@ */ import React, { useMemo } from 'react'; -import { ConfigKey, MonitorFields, TLSFields, DataStream } from '../../../common/runtime_types'; +import { + ConfigKey, + MonitorFields, + TLSFields, + DataStream, + ScheduleUnit, +} from '../../../common/runtime_types'; import { useTrackPageview } from '../../../../observability/public'; import { SyntheticsProviders } from '../fleet_package/contexts'; import { PolicyConfig } from '../fleet_package/types'; @@ -71,6 +77,8 @@ export const EditMonitorConfig = ({ monitor }: Props) => { defaultName: defaultConfig?.name || '', // TODO - figure out typing concerns for name defaultLocations: defaultConfig.locations, isEditable: true, + isZipUrlSourceEnabled: false, + allowedScheduleUnits: [ScheduleUnit.MINUTES], }} httpDefaultValues={fullDefaultConfig[DataStream.HTTP]} tcpDefaultValues={fullDefaultConfig[DataStream.TCP]} diff --git a/x-pack/plugins/uptime/public/pages/monitor_management/add_monitor.tsx b/x-pack/plugins/uptime/public/pages/monitor_management/add_monitor.tsx index 9d4546436defa..749a109dffda2 100644 --- a/x-pack/plugins/uptime/public/pages/monitor_management/add_monitor.tsx +++ b/x-pack/plugins/uptime/public/pages/monitor_management/add_monitor.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { useTrackPageview } from '../../../../observability/public'; +import { ScheduleUnit } from '../../../common/runtime_types'; import { SyntheticsProviders } from '../../components/fleet_package/contexts'; import { Loader } from '../../components/monitor_management/loader/loader'; import { MonitorConfig } from '../../components/monitor_management/monitor_config/monitor_config'; @@ -27,7 +28,12 @@ export const AddMonitorPage: React.FC = () => { errorTitle={ERROR_HEADING_LABEL} errorBody={ERROR_BODY_LABEL} > - + From a03430187709b05213878041ec168b299ee347cd Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 6 Jan 2022 12:46:06 -0500 Subject: [PATCH 4/6] skip failing test suite (#100236) --- .../security_solution_endpoint/apps/endpoint/policy_details.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 54fbf9d63b2bd..0cf86e891e143 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -236,7 +236,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); }; - describe('When on the Endpoint Policy Details Page', function () { + // Failing: See https://github.com/elastic/kibana/issues/100236 + describe.skip('When on the Endpoint Policy Details Page', function () { let indexedData: IndexedHostsAndAlertsResponse; before(async () => { From 47c39a26082f75aeea061e0008910315c6959420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Thu, 6 Jan 2022 19:04:32 +0100 Subject: [PATCH 5/6] [Security Solution] [Endpoint] Allows user remove event filter from policy using a modal (#122315) * Allows user remove event filter from policy using a modal. Adds unit tests and includes some code improvements * Remove unused code from service * Remove unused code, fix ts errors and use eventFiltersHttpMock instead of a service mock Co-authored-by: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> --- .../pages/event_filters/service/index.ts | 37 +++--- .../view/event_filters/delete_modal/index.ts | 8 ++ ...policy_event_filters_delete_modal.test.tsx | 117 ++++++++++++++++++ .../policy_event_filters_delete_modal.tsx | 116 +++++++++++++++++ .../policy_event_filters_flyout.test.tsx | 36 ++---- .../pages/policy/view/event_filters/hooks.ts | 10 +- .../policy_event_filters_layout.test.tsx | 70 ++++------- .../list/policy_event_filters_list.tsx | 41 +++++- .../fleet_event_filters_card.test.tsx | 3 +- 9 files changed, 346 insertions(+), 92 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/index.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts index a4ac378610e49..7b2d98d72b0c9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts @@ -90,22 +90,8 @@ export class EventFiltersHttpService implements EventFiltersService { async updateOne( exception: Immutable ): Promise { - const exceptionToUpdateCleaned = { ...exception }; - // Clean unnecessary fields for update action - [ - 'created_at', - 'created_by', - 'created_at', - 'created_by', - 'list_id', - 'tie_breaker_id', - 'updated_at', - 'updated_by', - ].forEach((field) => { - delete exceptionToUpdateCleaned[field as keyof UpdateExceptionListItemSchema]; - }); return (await this.httpWrapper()).put(EXCEPTION_LIST_ITEM_URL, { - body: JSON.stringify(exceptionToUpdateCleaned), + body: JSON.stringify(EventFiltersHttpService.cleanEventFilterToUpdate(exception)), }); } @@ -129,4 +115,25 @@ export class EventFiltersHttpService implements EventFiltersService { } ); } + + static cleanEventFilterToUpdate( + exception: Immutable + ): UpdateExceptionListItemSchema { + const exceptionToUpdateCleaned = { ...exception }; + // Clean unnecessary fields for update action + [ + 'created_at', + 'created_by', + 'created_at', + 'created_by', + 'list_id', + 'tie_breaker_id', + 'updated_at', + 'updated_by', + ].forEach((field) => { + delete exceptionToUpdateCleaned[field as keyof UpdateExceptionListItemSchema]; + }); + + return exceptionToUpdateCleaned as UpdateExceptionListItemSchema; + } } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/index.ts new file mode 100644 index 0000000000000..4dd64e5c2f938 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { PolicyEventFiltersDeleteModal } from './policy_event_filters_delete_modal'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx new file mode 100644 index 0000000000000..6711b48326bbf --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx @@ -0,0 +1,117 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { act, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import uuid from 'uuid'; +import { getExceptionListItemSchemaMock } from '../../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; +import { + AppContextTestRender, + createAppRootMockRenderer, +} from '../../../../../../common/mock/endpoint'; +import { PolicyEventFiltersDeleteModal } from './policy_event_filters_delete_modal'; +import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_utils'; +import { EventFiltersHttpService } from '../../../../event_filters/service'; + +describe('Policy details event filter delete modal', () => { + let policyId: string; + let render: () => Promise>; + let renderResult: ReturnType; + let mockedContext: AppContextTestRender; + let exception: ExceptionListItemSchema; + let mockedApi: ReturnType; + let onCancel: () => void; + + beforeEach(() => { + policyId = uuid.v4(); + mockedContext = createAppRootMockRenderer(); + exception = getExceptionListItemSchemaMock(); + onCancel = jest.fn(); + mockedApi = eventFiltersListQueryHttpMock(mockedContext.coreStart.http); + render = async () => { + await act(async () => { + renderResult = mockedContext.render( + + ); + await waitFor(mockedApi.responseProvider.eventFiltersList); + }); + return renderResult; + }; + }); + + it('should render with enabled buttons', async () => { + await render(); + expect(renderResult.getByTestId('confirmModalCancelButton')).toBeEnabled(); + expect(renderResult.getByTestId('confirmModalConfirmButton')).toBeEnabled(); + }); + + it('should disable the submit button while deleting ', async () => { + await render(); + const confirmButton = renderResult.getByTestId('confirmModalConfirmButton'); + userEvent.click(confirmButton); + + await waitFor(() => { + expect(confirmButton).toBeDisabled(); + }); + }); + + it('should call the API with the removed policy from the exception tags', async () => { + exception.tags = ['policy:1234', 'policy:4321', `policy:${policyId}`, 'not-a-policy-tag']; + await render(); + const confirmButton = renderResult.getByTestId('confirmModalConfirmButton'); + userEvent.click(confirmButton); + await waitFor(() => { + expect(mockedApi.responseProvider.eventFiltersUpdateOne).toHaveBeenLastCalledWith({ + body: JSON.stringify( + EventFiltersHttpService.cleanEventFilterToUpdate({ + ...exception, + tags: ['policy:1234', 'policy:4321', 'not-a-policy-tag'], + }) + ), + path: '/api/exception_lists/items', + }); + }); + }); + + it('should show a success toast if the operation was success', async () => { + await render(); + const confirmButton = renderResult.getByTestId('confirmModalConfirmButton'); + userEvent.click(confirmButton); + + await waitFor(() => { + expect(mockedApi.responseProvider.eventFiltersUpdateOne).toHaveBeenCalled(); + }); + + expect(onCancel).toHaveBeenCalled(); + expect(mockedContext.coreStart.notifications.toasts.addSuccess).toHaveBeenCalled(); + }); + + it('should show an error toast if the operation failed', async () => { + const error = new Error('the server is too far away'); + mockedApi.responseProvider.eventFiltersUpdateOne.mockImplementation(() => { + throw error; + }); + + await render(); + const confirmButton = renderResult.getByTestId('confirmModalConfirmButton'); + userEvent.click(confirmButton); + + await waitFor(() => { + expect(mockedApi.responseProvider.eventFiltersUpdateOne).toHaveBeenCalled(); + }); + + expect(mockedContext.coreStart.notifications.toasts.addError).toHaveBeenCalledWith(error, { + title: 'Error while attempt to remove event filter', + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx new file mode 100644 index 0000000000000..eca26a0026dd1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx @@ -0,0 +1,116 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCallOut, EuiConfirmModal, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import React, { useCallback } from 'react'; +import { useToasts } from '../../../../../../common/lib/kibana'; +import { ServerApiError } from '../../../../../../common/types'; +import { useBulkUpdateEventFilters } from '../hooks'; + +export const PolicyEventFiltersDeleteModal = ({ + policyId, + exception, + onCancel, +}: { + policyId: string; + exception: ExceptionListItemSchema; + onCancel: () => void; +}) => { + const toasts = useToasts(); + + const { mutate: updateEventFilter, isLoading: isUpdateEventFilterLoading } = + useBulkUpdateEventFilters({ + onUpdateSuccess: () => { + toasts.addSuccess({ + title: i18n.translate( + 'xpack.securitySolution.endpoint.policy.eventFilters.list.removeDialog.successToastTitle', + { defaultMessage: 'Successfully removed' } + ), + text: i18n.translate( + 'xpack.securitySolution.endpoint.policy.eventFilters.list.removeDialog.successToastText', + { + defaultMessage: '"{exception}" has been removed from policy', + values: { exception: exception.name }, + } + ), + }); + onCancel(); + }, + onUpdateError: (error?: ServerApiError) => { + toasts.addError(error as unknown as Error, { + title: i18n.translate( + 'xpack.securitySolution.endpoint.policy.eventFilters.list.removeDialog.errorToastTitle', + { + defaultMessage: 'Error while attempt to remove event filter', + } + ), + }); + onCancel(); + }, + onSettledCallback: onCancel, + }); + + const handleModalConfirm = useCallback(() => { + const modifiedException = { + ...exception, + tags: exception.tags.filter((tag) => tag !== `policy:${policyId}`), + }; + updateEventFilter([modifiedException]); + }, [exception, policyId, updateEventFilter]); + + const handleCancel = useCallback(() => { + if (!isUpdateEventFilterLoading) { + onCancel(); + } + }, [isUpdateEventFilterLoading, onCancel]); + + return ( + + +

+ +

+
+ + + + +

+ +

+
+
+ ); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/policy_event_filters_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/policy_event_filters_flyout.test.tsx index 092a787d1f7e0..ef1cbc8163705 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/policy_event_filters_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/policy_event_filters_flyout.test.tsx @@ -17,7 +17,6 @@ import { } from '../../../../../../common/mock/endpoint'; import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data'; import { PolicyData } from '../../../../../../../common/endpoint/types'; -import { getPolicyEventFiltersPath } from '../../../../../common/routing'; import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_utils'; import { PolicyEventFiltersFlyout } from './policy_event_filters_flyout'; import { parseQueryFilterToKQL, parsePoliciesAndFilterToKql } from '../../../../../common/utils'; @@ -26,8 +25,8 @@ import { FoundExceptionListItemSchema, UpdateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; +import { EventFiltersHttpService } from '../../../../event_filters/service'; -const endpointGenerator = new EndpointDocGenerator('seed'); const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({ path: '/api/exception_lists/items/_find', query: { @@ -40,53 +39,40 @@ const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({ sort_order: undefined, }, }); -const emptyList = { +const getEmptyList = () => ({ data: [], page: 1, per_page: 10, total: 0, -}; +}); const getCleanedExceptionWithNewTags = ( exception: UpdateExceptionListItemSchema, testTags: string[], policy: PolicyData ) => { - const exceptionToUpdateCleaned = { + const exceptionToUpdateWithNewTags = { ...exception, tags: [...testTags, `policy:${policy.id}`], }; - // Clean unnecessary fields for update action - [ - 'created_at', - 'created_by', - 'created_at', - 'created_by', - 'list_id', - 'tie_breaker_id', - 'updated_at', - 'updated_by', - ].forEach((field) => { - delete exceptionToUpdateCleaned[field as keyof UpdateExceptionListItemSchema]; - }); - return exceptionToUpdateCleaned; + + return EventFiltersHttpService.cleanEventFilterToUpdate(exceptionToUpdateWithNewTags); }; describe('Policy details event filters flyout', () => { let render: () => Promise>; let renderResult: ReturnType; - let history: AppContextTestRender['history']; let mockedContext: AppContextTestRender; let mockedApi: ReturnType; let policy: PolicyData; let onCloseMock: jest.Mock; beforeEach(() => { + const endpointGenerator = new EndpointDocGenerator('seed'); policy = endpointGenerator.generatePolicyPackagePolicy(); mockedContext = createAppRootMockRenderer(); mockedApi = eventFiltersListQueryHttpMock(mockedContext.coreStart.http); onCloseMock = jest.fn(); - ({ history } = mockedContext); render = async () => { await act(async () => { renderResult = mockedContext.render( @@ -96,8 +82,6 @@ describe('Policy details event filters flyout', () => { }); return renderResult; }; - - history.push(getPolicyEventFiltersPath(policy.id)); }); it('should render a list of assignable policies and searchbar', async () => { @@ -126,7 +110,7 @@ describe('Policy details event filters flyout', () => { expect(await renderResult.findByTestId('artifactsList')).toBeTruthy(); // results for search - mockedApi.responseProvider.eventFiltersList.mockImplementationOnce(() => emptyList); + mockedApi.responseProvider.eventFiltersList.mockImplementationOnce(() => getEmptyList()); // do a search userEvent.type(renderResult.getByTestId('searchField'), 'no results with this{enter}'); @@ -146,7 +130,7 @@ describe('Policy details event filters flyout', () => { it('should render "not assignable items" when no possible exceptions can be assigned', async () => { // both exceptions list requests will return no results - mockedApi.responseProvider.eventFiltersList.mockImplementation(() => emptyList); + mockedApi.responseProvider.eventFiltersList.mockImplementation(() => getEmptyList()); await render(); expect(await renderResult.findByTestId('eventFilters-no-assignable-items')).toBeTruthy(); }); @@ -194,7 +178,7 @@ describe('Policy details event filters flyout', () => { beforeEach(async () => { exceptions = { - ...emptyList, + ...getEmptyList(), total: 2, data: [ getExceptionListItemSchemaMock({ diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/hooks.ts index 571dd8779d093..fd5e00668c164 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/hooks.ts @@ -71,7 +71,6 @@ export function useSearchAssignedEventFilters( } ); } - export function useSearchNotAssignedEventFilters( policyId: string, options: { filter?: string; perPage?: number; enabled?: boolean } @@ -103,7 +102,7 @@ export function useSearchNotAssignedEventFilters( export function useBulkUpdateEventFilters( callbacks: { onUpdateSuccess?: (updatedExceptions: ExceptionListItemSchema[]) => void; - onUpdateError?: () => void; + onUpdateError?: (error?: ServerApiError) => void; onSettledCallback?: () => void; } = {} ) { @@ -116,7 +115,12 @@ export function useBulkUpdateEventFilters( onSettledCallback = () => {}, } = callbacks; - return useMutation( + return useMutation< + ExceptionListItemSchema[], + ServerApiError, + ExceptionListItemSchema[], + () => void + >( (eventFilters: ExceptionListItemSchema[]) => { return pMap( eventFilters, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/policy_event_filters_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/policy_event_filters_layout.test.tsx index e834838cc37ee..f1aedf3e2d045 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/policy_event_filters_layout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/policy_event_filters_layout.test.tsx @@ -12,24 +12,22 @@ import { createAppRootMockRenderer, } from '../../../../../../common/mock/endpoint'; import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data'; -import { EventFilterGenerator } from '../../../../../../../common/endpoint/data_generators/event_filter_generator'; -import { EventFiltersHttpService } from '../../../../event_filters/service'; import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types'; import { parsePoliciesAndFilterToKql } from '../../../../../common/utils'; - -jest.mock('../../../../event_filters/service'); -const EventFiltersHttpServiceMock = EventFiltersHttpService as jest.Mock; +import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_utils'; +import { getFoundExceptionListItemSchemaMock } from '../../../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock'; let render: () => ReturnType; let mockedContext: AppContextTestRender; let policyItem: ImmutableObject; const generator = new EndpointDocGenerator(); -const eventFilterGenerator = new EventFilterGenerator(); +let mockedApi: ReturnType; describe('Policy event filters layout', () => { beforeEach(() => { mockedContext = createAppRootMockRenderer(); + mockedApi = eventFiltersListQueryHttpMock(mockedContext.coreStart.http); policyItem = generator.generatePolicyPackagePolicy(); render = () => mockedContext.render(); }); @@ -42,44 +40,29 @@ describe('Policy event filters layout', () => { }); it('should renders layout with no assigned event filters data when there are not event filters', async () => { - EventFiltersHttpServiceMock.mockImplementation(() => { - return { - getList: () => ({ - total: 0, - data: [], - }), - }; - }); + mockedApi.responseProvider.eventFiltersList.mockReturnValue( + getFoundExceptionListItemSchemaMock(0) + ); const component = render(); expect(await component.findByTestId('policy-event-filters-empty-unexisting')).not.toBeNull(); }); it('should renders layout with no assigned event filters data when there are event filters', async () => { - EventFiltersHttpServiceMock.mockImplementation(() => { - return { - getList: ( - params: Partial<{ - filter: string; - }> - ) => { - if ( - params && - params.filter === parsePoliciesAndFilterToKql({ policies: [policyItem.id, 'all'] }) - ) { - return { - total: 0, - data: [], - }; - } else { - return { - total: 1, - data: [eventFilterGenerator.generate()], - }; - } - }, - }; - }); + mockedApi.responseProvider.eventFiltersList.mockImplementation( + // @ts-expect-error + (args) => { + const params = args.query; + if ( + params && + params.filter === parsePoliciesAndFilterToKql({ policies: [policyItem.id, 'all'] }) + ) { + return getFoundExceptionListItemSchemaMock(0); + } else { + return getFoundExceptionListItemSchemaMock(1); + } + } + ); const component = render(); @@ -87,14 +70,9 @@ describe('Policy event filters layout', () => { }); it('should renders layout with data', async () => { - EventFiltersHttpServiceMock.mockImplementation(() => { - return { - getList: () => ({ - total: 3, - data: Array.from({ length: 3 }, () => eventFilterGenerator.generate()), - }), - }; - }); + mockedApi.responseProvider.eventFiltersList.mockReturnValue( + getFoundExceptionListItemSchemaMock(3) + ); const component = render(); expect(await component.findByTestId('policy-event-filters-header-section')).not.toBeNull(); expect(await component.findByTestId('policy-event-filters-layout-about')).not.toBeNull(); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx index b837a99370218..4153eb45200ed 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx @@ -29,6 +29,8 @@ import { } from '../../policy_hooks'; import { getCurrentArtifactsLocation } from '../../../store/policy_details/selectors'; import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types'; +import { PolicyEventFiltersDeleteModal } from '../delete_modal'; +import { isGlobalPolicyEffected } from '../../../../../components/effected_policy_select/utils'; import { getEventFiltersListPath } from '../../../../../common/routing'; interface PolicyEventFiltersListProps { @@ -40,6 +42,9 @@ export const PolicyEventFiltersList = React.memo(({ const navigateCallback = usePolicyDetailsEventFiltersNavigateCallback(); const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation); const [expandedItemsMap, setExpandedItemsMap] = useState>(new Map()); + const [exceptionItemToDelete, setExceptionItemToDelete] = useState< + ExceptionListItemSchema | undefined + >(); const { data: eventFilters, @@ -112,15 +117,49 @@ export const PolicyEventFiltersList = React.memo(({ 'data-test-subj': 'view-full-details-action', }; const item = artifact as ExceptionListItemSchema; + + const isGlobal = isGlobalPolicyEffected(item.tags); + const deleteAction = { + icon: 'trash', + children: i18n.translate( + 'xpack.securitySolution.endpoint.policy.eventFilters.list.removeAction', + { defaultMessage: 'Remove from policy' } + ), + onClick: () => { + setExceptionItemToDelete(item); + }, + disabled: isGlobal, + toolTipContent: isGlobal + ? i18n.translate( + 'xpack.securitySolution.endpoint.policy.eventFilters.list.removeActionNotAllowed', + { + defaultMessage: 'Globally applied event filters cannot be removed from policy.', + } + ) + : undefined, + toolTipPosition: 'top' as const, + 'data-test-subj': 'remove-from-policy-action', + }; return { expanded: expandedItemsMap.get(item.id) || false, - actions: [fullDetailsAction], + actions: [fullDetailsAction, deleteAction], policies: artifactCardPolicies, }; }; + const handleDeleteModalClose = useCallback(() => { + setExceptionItemToDelete(undefined); + }, [setExceptionItemToDelete]); + return ( <> + {exceptionItemToDelete && ( + + )} Date: Thu, 6 Jan 2022 13:33:52 -0500 Subject: [PATCH 6/6] [Controls] Move Controls To Their Own Plugin (#121668) * Moved controls out of Presentation Util and into their own plugin --- .github/CODEOWNERS | 1 + .i18nrc.json | 1 + docs/developer/plugin-list.asciidoc | 4 + packages/kbn-optimizer/limits.yml | 3 +- src/dev/storybook/aliases.ts | 1 + src/plugins/controls/README.mdx | 25 +++ .../control_group_persistable_state.ts | 4 +- .../common}/control_group/types.ts | 2 +- .../options_list_persistable_state.ts | 6 +- .../control_types/options_list/types.ts | 0 src/plugins/controls/common/index.ts | 14 ++ .../controls => controls/common}/types.ts | 4 +- src/plugins/controls/kibana.json | 23 ++ .../public}/__stories__/controls.stories.tsx | 31 ++- .../public}/__stories__/decorators.tsx | 0 .../storybook_control_factories.ts | 6 +- .../component/control_frame_component.tsx | 8 +- .../component/control_group_component.tsx | 8 +- .../component/control_group_sortable_item.tsx | 2 +- .../public}/control_group/control_group.scss | 0 .../control_group/control_group_strings.ts | 194 +++++++++++++++++ .../control_group/editor/control_editor.tsx | 0 .../control_group/editor/create_control.tsx | 10 +- .../control_group/editor/edit_control.tsx | 10 +- .../editor/edit_control_group.tsx | 4 +- .../control_group/editor/editor_constants.ts | 0 .../editor/forward_all_context.tsx | 17 +- .../embeddable/control_group_container.tsx | 25 ++- .../control_group_container_factory.ts | 7 +- .../public}/control_group/index.ts | 4 +- .../public}/control_group/opt_a.svg | 0 .../state/control_group_reducers.ts | 0 .../public}/control_group/types.ts | 9 +- .../public}/control_types/index.ts | 0 .../control_types/options_list/index.ts | 3 + .../options_list/options_list.scss | 0 .../options_list/options_list_component.tsx | 2 +- .../options_list/options_list_editor.tsx | 18 +- .../options_list/options_list_embeddable.tsx | 47 ++-- .../options_list_embeddable_factory.tsx | 4 +- .../options_list_popover_component.tsx | 2 +- .../options_list/options_list_reducers.ts | 0 .../options_list/options_list_strings.ts | 28 +-- .../control_types/options_list/types.ts | 2 +- .../public}/controls_service.ts | 4 +- .../public}/hooks/use_child_embeddable.ts | 0 .../public}/hooks/use_state_observable.ts | 0 src/plugins/controls/public/index.ts | 44 ++++ src/plugins/controls/public/plugin.ts | 95 ++++++++ .../public/services/controls.ts | 9 +- .../public/services/data.ts | 2 +- .../controls/public/services/data_views.ts | 15 ++ src/plugins/controls/public/services/index.ts | 32 +++ .../public/services/kibana}/controls.ts | 6 +- .../public/services/kibana/data.ts | 10 +- .../public/services/kibana/data_views.ts | 28 +++ .../controls/public/services/kibana/index.ts | 36 +++ .../public/services/kibana/overlays.ts | 10 +- .../public/services/overlays.ts | 2 +- .../public/services/storybook}/controls.ts | 6 +- .../public/services/storybook/data.ts | 6 +- .../public/services/storybook/data_views.ts | 29 +++ .../public/services/storybook/index.ts | 32 +++ .../public/services/storybook/overlays.tsx | 8 +- .../public/services/stub}/controls.ts | 6 +- .../controls/public/services/stub/index.ts | 28 +++ .../public/services/stub/overlays.ts | 6 +- .../controls => controls/public}/types.ts | 38 +++- .../control_group_container_factory.ts | 8 +- .../options_list_embeddable_factory.ts | 6 +- .../controls => controls/server}/index.ts | 6 +- src/plugins/controls/server/plugin.ts | 33 +++ src/plugins/controls/storybook/decorator.tsx | 46 ++++ src/plugins/controls/storybook/main.ts | 16 ++ src/plugins/controls/storybook/manager.ts | 26 +++ src/plugins/controls/storybook/preview.tsx | 29 +++ src/plugins/controls/tsconfig.json | 27 +++ .../dashboard_container_persistable_state.ts | 2 +- .../common/saved_dashboard_references.ts | 2 +- src/plugins/dashboard/common/types.ts | 2 +- src/plugins/dashboard/kibana.json | 1 + .../embeddable/dashboard_container.tsx | 2 +- .../dashboard_container_factory.tsx | 2 +- .../viewport/dashboard_viewport.tsx | 2 +- .../lib/convert_dashboard_state.ts | 2 +- .../lib/dashboard_control_group.ts | 2 +- .../dashboard/public/dashboard_constants.ts | 2 +- .../saved_dashboards/saved_dashboard.ts | 2 +- src/plugins/dashboard/tsconfig.json | 13 +- .../expression_renderers/image_renderer.tsx | 2 +- src/plugins/presentation_util/README.mdx | 95 ++++++++ .../presentation_util/common/lib/index.ts | 1 - src/plugins/presentation_util/kibana.json | 13 +- .../__stories__/fixtures/flights.ts | 8 +- .../__stories__/fixtures/flights_data.ts | 0 .../public/__stories__/index.tsx | 2 + .../control_group/control_group_strings.ts | 206 ------------------ .../data_view_picker.stories.tsx | 2 +- .../data_view_picker/data_view_picker.tsx | 4 + .../field_picker/field_picker.stories.tsx | 2 +- .../components/field_picker/field_picker.tsx | 4 + .../public/components/index.tsx | 9 + .../components/redux_embeddables}/index.ts | 11 +- .../redux_embeddable_context.ts | 4 +- .../redux_embeddable_wrapper.tsx | 25 ++- .../components/redux_embeddables/types.ts | 6 + src/plugins/presentation_util/public/index.ts | 11 +- src/plugins/presentation_util/public/mocks.ts | 7 +- .../presentation_util/public/plugin.ts | 52 ----- .../public/services/index.ts | 7 - .../public/services/kibana/index.ts | 6 - .../public/services/storybook/index.ts | 6 - .../public/services/stub/index.ts | 6 - src/plugins/presentation_util/public/types.ts | 15 +- .../presentation_util/server/plugin.ts | 17 +- src/plugins/presentation_util/tsconfig.json | 7 +- .../page_objects/dashboard_page_controls.ts | 3 +- .../canvas/public/functions/pie.test.js | 2 +- .../canvas/public/functions/plot.test.js | 2 +- 119 files changed, 1203 insertions(+), 544 deletions(-) create mode 100644 src/plugins/controls/README.mdx rename src/plugins/{presentation_util/common/controls => controls/common}/control_group/control_group_persistable_state.ts (96%) rename src/plugins/{presentation_util/common/controls => controls/common}/control_group/types.ts (91%) rename src/plugins/{presentation_util/common/controls => controls/common}/control_types/options_list/options_list_persistable_state.ts (89%) rename src/plugins/{presentation_util/common/controls => controls/common}/control_types/options_list/types.ts (100%) create mode 100644 src/plugins/controls/common/index.ts rename src/plugins/{presentation_util/common/controls => controls/common}/types.ts (87%) create mode 100644 src/plugins/controls/kibana.json rename src/plugins/{presentation_util/public/components/controls => controls/public}/__stories__/controls.stories.tsx (83%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/__stories__/decorators.tsx (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/__stories__/storybook_control_factories.ts (83%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/component/control_frame_component.tsx (96%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/component/control_group_component.tsx (96%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/component/control_group_sortable_item.tsx (98%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/control_group.scss (100%) create mode 100644 src/plugins/controls/public/control_group/control_group_strings.ts rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/editor/control_editor.tsx (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/editor/create_control.tsx (94%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/editor/edit_control.tsx (93%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/editor/edit_control_group.tsx (97%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/editor/editor_constants.ts (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/editor/forward_all_context.tsx (67%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/embeddable/control_group_container.tsx (89%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/embeddable/control_group_container_factory.ts (91%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/index.ts (92%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/opt_a.svg (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/state/control_group_reducers.ts (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_group/types.ts (69%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/index.ts (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/index.ts (72%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/options_list.scss (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/options_list_component.tsx (97%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/options_list_editor.tsx (90%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/options_list_embeddable.tsx (89%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/options_list_embeddable_factory.tsx (93%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/options_list_popover_component.tsx (98%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/options_list_reducers.ts (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/options_list_strings.ts (59%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/control_types/options_list/types.ts (81%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/controls_service.ts (88%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/hooks/use_child_embeddable.ts (100%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/hooks/use_state_observable.ts (100%) create mode 100644 src/plugins/controls/public/index.ts create mode 100644 src/plugins/controls/public/plugin.ts rename src/plugins/{presentation_util => controls}/public/services/controls.ts (89%) rename src/plugins/{presentation_util => controls}/public/services/data.ts (91%) create mode 100644 src/plugins/controls/public/services/data_views.ts create mode 100644 src/plugins/controls/public/services/index.ts rename src/plugins/{presentation_util/public/services/stub => controls/public/services/kibana}/controls.ts (65%) rename src/plugins/{presentation_util => controls}/public/services/kibana/data.ts (71%) create mode 100644 src/plugins/controls/public/services/kibana/data_views.ts create mode 100644 src/plugins/controls/public/services/kibana/index.ts rename src/plugins/{presentation_util => controls}/public/services/kibana/overlays.ts (71%) rename src/plugins/{presentation_util => controls}/public/services/overlays.ts (93%) rename src/plugins/{presentation_util/public/services/kibana => controls/public/services/storybook}/controls.ts (65%) rename src/plugins/{presentation_util => controls}/public/services/storybook/data.ts (83%) create mode 100644 src/plugins/controls/public/services/storybook/data_views.ts create mode 100644 src/plugins/controls/public/services/storybook/index.ts rename src/plugins/{presentation_util => controls}/public/services/storybook/overlays.tsx (95%) rename src/plugins/{presentation_util/public/services/storybook => controls/public/services/stub}/controls.ts (65%) create mode 100644 src/plugins/controls/public/services/stub/index.ts rename src/plugins/{presentation_util => controls}/public/services/stub/overlays.ts (81%) rename src/plugins/{presentation_util/public/components/controls => controls/public}/types.ts (60%) rename src/plugins/{presentation_util/server/controls => controls/server}/control_group/control_group_container_factory.ts (71%) rename src/plugins/{presentation_util/server/controls => controls/server}/control_types/options_list/options_list_embeddable_factory.ts (72%) rename src/plugins/{presentation_util/public/components/controls => controls/server}/index.ts (79%) create mode 100644 src/plugins/controls/server/plugin.ts create mode 100644 src/plugins/controls/storybook/decorator.tsx create mode 100644 src/plugins/controls/storybook/main.ts create mode 100644 src/plugins/controls/storybook/manager.ts create mode 100644 src/plugins/controls/storybook/preview.tsx create mode 100644 src/plugins/controls/tsconfig.json rename src/plugins/presentation_util/public/{components/controls => }/__stories__/fixtures/flights.ts (93%) rename src/plugins/presentation_util/public/{components/controls => }/__stories__/fixtures/flights_data.ts (100%) delete mode 100644 src/plugins/presentation_util/public/components/controls/control_group/control_group_strings.ts rename src/plugins/presentation_util/{common/controls => public/components/redux_embeddables}/index.ts (59%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 74d2138a9404f..37b0763c5e96e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -163,6 +163,7 @@ /src/plugins/input_control_vis/ @elastic/kibana-presentation /src/plugins/vis_type_markdown/ @elastic/kibana-presentation /src/plugins/presentation_util/ @elastic/kibana-presentation +/src/plugins/controls/ @elastic/kibana-presentation /test/functional/apps/dashboard/ @elastic/kibana-presentation /test/functional/apps/dashboard_elements/ @elastic/kibana-presentation /x-pack/plugins/canvas/ @elastic/kibana-presentation diff --git a/.i18nrc.json b/.i18nrc.json index 207e1778213bb..c348fc2c2b60c 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -9,6 +9,7 @@ "discover": "src/plugins/discover", "bfetch": "src/plugins/bfetch", "dashboard": "src/plugins/dashboard", + "controls": "src/plugins/controls", "data": "src/plugins/data", "dataViews": "src/plugins/data_views", "embeddableApi": "src/plugins/embeddable", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index eb4fd6e30f304..f94b68fe9ab36 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -40,6 +40,10 @@ as uiSettings within the code. |Console provides the user with tools for storing and executing requests against Elasticsearch. +|{kib-repo}blob/{branch}/src/plugins/controls/README.mdx[controls] +|The Controls plugin contains Embeddables which can be used to add user-friendly interactivity to apps. + + |{kib-repo}blob/{branch}/src/plugins/custom_integrations/README.md[customIntegrations] |Register add-data cards diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index dccf2f2c14169..8865258b36d1b 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -75,7 +75,7 @@ pageLoadAssetSize: watcher: 43598 runtimeFields: 41752 stackAlerts: 29684 - presentationUtil: 84606 + presentationUtil: 58834 osquery: 107090 fileUpload: 25664 dataVisualizer: 27530 @@ -119,4 +119,5 @@ pageLoadAssetSize: visTypeHeatmap: 25340 screenshotting: 17017 expressionGauge: 25000 + controls: 34788 expressionPie: 26338 diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index d6f7fedccd2a2..5043312d0b25c 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -33,5 +33,6 @@ export const storybookAliases = { ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook', observability: 'x-pack/plugins/observability/.storybook', presentation: 'src/plugins/presentation_util/storybook', + controls: 'src/plugins/controls/storybook', lists: 'x-pack/plugins/lists/.storybook', }; diff --git a/src/plugins/controls/README.mdx b/src/plugins/controls/README.mdx new file mode 100644 index 0000000000000..46ba1ed3ba9e7 --- /dev/null +++ b/src/plugins/controls/README.mdx @@ -0,0 +1,25 @@ +--- +id: controls +slug: /kibana-dev-docs/controls +title: Controls Plugin +summary: Introduction to the Controls Plugin. +date: 2020-01-12 +tags: ['kibana', 'controls', 'dashboard'] +related: [] +--- + +## Introduction + +The Controls plugin contains Embeddables which can be used to add user-friendly interactivity to apps. + +## The Control Group + +The Control group is an embeddable container which provides the ability to add, remove, reorder, and edit multiple types of control embeddable. In any implementation, the control group embeddable should be the main point of contact between the application and the controls. The list of filters it sends to its output observable should be considered the final output of the current state of the controls, and can then be sent to other embeddables, or combined with filters from other sources. + +## Control Types + +Multiple types of controls can be registered to work with the Control Group. The current implementations are as follows: + +### Options List + +The options list is the most basic, and most used control type. It allows the dashboard author to specify a data view and field, and create a searchable dropdown. diff --git a/src/plugins/presentation_util/common/controls/control_group/control_group_persistable_state.ts b/src/plugins/controls/common/control_group/control_group_persistable_state.ts similarity index 96% rename from src/plugins/presentation_util/common/controls/control_group/control_group_persistable_state.ts rename to src/plugins/controls/common/control_group/control_group_persistable_state.ts index 2da488acdc436..0fd24bd234327 100644 --- a/src/plugins/presentation_util/common/controls/control_group/control_group_persistable_state.ts +++ b/src/plugins/controls/common/control_group/control_group_persistable_state.ts @@ -10,9 +10,9 @@ import { EmbeddableInput, EmbeddablePersistableStateService, EmbeddableStateWithType, -} from '../../../../embeddable/common/types'; +} from '../../../embeddable/common/types'; import { ControlGroupInput, ControlPanelState } from './types'; -import { SavedObjectReference } from '../../../../../core/types'; +import { SavedObjectReference } from '../../../../core/types'; type ControlGroupInputWithType = Partial & { type: string }; diff --git a/src/plugins/presentation_util/common/controls/control_group/types.ts b/src/plugins/controls/common/control_group/types.ts similarity index 91% rename from src/plugins/presentation_util/common/controls/control_group/types.ts rename to src/plugins/controls/common/control_group/types.ts index da1cec0391102..4e1bddc08143f 100644 --- a/src/plugins/presentation_util/common/controls/control_group/types.ts +++ b/src/plugins/controls/common/control_group/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EmbeddableInput, PanelState } from '../../../../embeddable/common/types'; +import { EmbeddableInput, PanelState } from '../../../embeddable/common/types'; import { ControlInput, ControlStyle, ControlWidth } from '../types'; export const CONTROL_GROUP_TYPE = 'control_group'; diff --git a/src/plugins/presentation_util/common/controls/control_types/options_list/options_list_persistable_state.ts b/src/plugins/controls/common/control_types/options_list/options_list_persistable_state.ts similarity index 89% rename from src/plugins/presentation_util/common/controls/control_types/options_list/options_list_persistable_state.ts rename to src/plugins/controls/common/control_types/options_list/options_list_persistable_state.ts index 90390256325ae..a41eb788d71e2 100644 --- a/src/plugins/presentation_util/common/controls/control_types/options_list/options_list_persistable_state.ts +++ b/src/plugins/controls/common/control_types/options_list/options_list_persistable_state.ts @@ -9,10 +9,10 @@ import { EmbeddableStateWithType, EmbeddablePersistableStateService, -} from '../../../../../embeddable/common/types'; +} from '../../../../embeddable/common'; import { OptionsListEmbeddableInput } from './types'; -import { SavedObjectReference } from '../../../../../../core/types'; -import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../../data_views/common'; +import { SavedObjectReference } from '../../../../../core/types'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../data_views/common'; type OptionsListInputWithType = Partial & { type: string }; const dataViewReferenceName = 'optionsListDataView'; diff --git a/src/plugins/presentation_util/common/controls/control_types/options_list/types.ts b/src/plugins/controls/common/control_types/options_list/types.ts similarity index 100% rename from src/plugins/presentation_util/common/controls/control_types/options_list/types.ts rename to src/plugins/controls/common/control_types/options_list/types.ts diff --git a/src/plugins/controls/common/index.ts b/src/plugins/controls/common/index.ts new file mode 100644 index 0000000000000..aa06259cf855e --- /dev/null +++ b/src/plugins/controls/common/index.ts @@ -0,0 +1,14 @@ +/* + * 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. + */ + +export type { ControlPanelState, ControlsPanels, ControlGroupInput } from './control_group/types'; +export type { OptionsListEmbeddableInput } from './control_types/options_list/types'; +export type { ControlWidth } from './types'; + +export { OPTIONS_LIST_CONTROL } from './control_types/options_list/types'; +export { CONTROL_GROUP_TYPE } from './control_group/types'; diff --git a/src/plugins/presentation_util/common/controls/types.ts b/src/plugins/controls/common/types.ts similarity index 87% rename from src/plugins/presentation_util/common/controls/types.ts rename to src/plugins/controls/common/types.ts index 288324e30b47c..ad03bb642f798 100644 --- a/src/plugins/presentation_util/common/controls/types.ts +++ b/src/plugins/controls/common/types.ts @@ -7,8 +7,8 @@ */ import { Filter, Query } from '@kbn/es-query'; -import { TimeRange } from '../../../data/common'; -import { EmbeddableInput } from '../../../embeddable/common/types'; +import { TimeRange } from '../../data/common'; +import { EmbeddableInput } from '../../embeddable/common/types'; export type ControlWidth = 'auto' | 'small' | 'medium' | 'large'; export type ControlStyle = 'twoLine' | 'oneLine'; diff --git a/src/plugins/controls/kibana.json b/src/plugins/controls/kibana.json new file mode 100644 index 0000000000000..20afd63505a73 --- /dev/null +++ b/src/plugins/controls/kibana.json @@ -0,0 +1,23 @@ +{ + "id": "controls", + "owner": { + "name": "Kibana Presentation", + "githubTeam": "kibana-presentation" + }, + "description": "The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "extraPublicDirs": ["common"], + "requiredPlugins": [ + "presentationUtil", + "savedObjects", + "kibanaReact", + "expressions", + "embeddable", + "dataViews", + "data" + ], + "optionalPlugins": [] +} diff --git a/src/plugins/presentation_util/public/components/controls/__stories__/controls.stories.tsx b/src/plugins/controls/public/__stories__/controls.stories.tsx similarity index 83% rename from src/plugins/presentation_util/public/components/controls/__stories__/controls.stories.tsx rename to src/plugins/controls/public/__stories__/controls.stories.tsx index e6fa5ef630904..ac181f2ab32dd 100644 --- a/src/plugins/presentation_util/public/components/controls/__stories__/controls.stories.tsx +++ b/src/plugins/controls/public/__stories__/controls.stories.tsx @@ -9,19 +9,28 @@ import { EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiTextAlign } from '@elastic/eui'; import React, { useEffect, useMemo, useState, useCallback, FC } from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; - import uuid from 'uuid'; + +import { + getFlightOptionsAsync, + storybookFlightsDataView, +} from '../../../presentation_util/public/mocks'; +import { + ControlGroupContainerFactory, + OptionsListEmbeddableInput, + OPTIONS_LIST_CONTROL, +} from '../'; + +import { ViewMode } from '../../../embeddable/public'; +import { EmbeddablePersistableStateService } from '../../../embeddable/common'; + import { decorators } from './decorators'; import { ControlsPanels } from '../control_group/types'; -import { ViewMode } from '../../../../../embeddable/public'; -import { getFlightOptionsAsync, storybookFlightsDataView } from './fixtures/flights'; -import { pluginServices, registry } from '../../../services/storybook'; -import { OptionsListEmbeddableInput, OPTIONS_LIST_CONTROL } from '../../..'; -import { replaceValueSuggestionMethod } from '../../../services/storybook/data'; -import { injectStorybookDataView } from '../../../services/storybook/data_views'; +import { ControlGroupContainer } from '../control_group'; +import { pluginServices, registry } from '../services/storybook'; +import { replaceValueSuggestionMethod } from '../services/storybook/data'; +import { injectStorybookDataView } from '../services/storybook/data_views'; import { populateStorybookControlFactories } from './storybook_control_factories'; -import { EmbeddablePersistableStateService } from '../../../../../embeddable/common'; -import { ControlGroupContainerFactory } from '../control_group/embeddable/control_group_container_factory'; export default { title: 'Controls', @@ -29,8 +38,6 @@ export default { decorators, }; -type EmbeddableType = Awaited>; - injectStorybookDataView(storybookFlightsDataView); replaceValueSuggestionMethod(getFlightOptionsAsync); @@ -39,7 +46,7 @@ const ControlGroupStoryComponent: FC<{ edit?: boolean; }> = ({ panels, edit }) => { const embeddableRoot: React.RefObject = useMemo(() => React.createRef(), []); - const [embeddable, setEmbeddable] = useState(); + const [embeddable, setEmbeddable] = useState(); const [viewMode, setViewMode] = useState( edit === undefined || edit ? ViewMode.EDIT : ViewMode.VIEW ); diff --git a/src/plugins/presentation_util/public/components/controls/__stories__/decorators.tsx b/src/plugins/controls/public/__stories__/decorators.tsx similarity index 100% rename from src/plugins/presentation_util/public/components/controls/__stories__/decorators.tsx rename to src/plugins/controls/public/__stories__/decorators.tsx diff --git a/src/plugins/presentation_util/public/components/controls/__stories__/storybook_control_factories.ts b/src/plugins/controls/public/__stories__/storybook_control_factories.ts similarity index 83% rename from src/plugins/presentation_util/public/components/controls/__stories__/storybook_control_factories.ts rename to src/plugins/controls/public/__stories__/storybook_control_factories.ts index e4429c1d69b13..9809e90bd12fc 100644 --- a/src/plugins/presentation_util/public/components/controls/__stories__/storybook_control_factories.ts +++ b/src/plugins/controls/public/__stories__/storybook_control_factories.ts @@ -7,12 +7,10 @@ */ import { OptionsListEmbeddableFactory } from '../control_types/options_list'; -import { PresentationControlsService } from '../../../services/controls'; +import { ControlsService } from '../services/controls'; import { ControlFactory } from '..'; -export const populateStorybookControlFactories = ( - controlsServiceStub: PresentationControlsService -) => { +export const populateStorybookControlFactories = (controlsServiceStub: ControlsService) => { const optionsListFactoryStub = new OptionsListEmbeddableFactory(); // cast to unknown because the stub cannot use the embeddable start contract to transform the EmbeddableFactoryDefinition into an EmbeddableFactory diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx b/src/plugins/controls/public/control_group/component/control_frame_component.tsx similarity index 96% rename from src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx rename to src/plugins/controls/public/control_group/component/control_frame_component.tsx index bdc3b2978f888..e921cbd90d298 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx +++ b/src/plugins/controls/public/control_group/component/control_frame_component.tsx @@ -18,11 +18,11 @@ import { } from '@elastic/eui'; import { ControlGroupInput } from '../types'; +import { pluginServices } from '../../services'; import { EditControlButton } from '../editor/edit_control'; -import { useChildEmbeddable } from '../../hooks/use_child_embeddable'; -import { useReduxContainerContext } from '../../../redux_embeddables/redux_embeddable_context'; import { ControlGroupStrings } from '../control_group_strings'; -import { pluginServices } from '../../../../services'; +import { useChildEmbeddable } from '../../hooks/use_child_embeddable'; +import { useReduxContainerContext } from '../../../../presentation_util/public'; export interface ControlFrameProps { customPrepend?: JSX.Element; @@ -38,7 +38,7 @@ export const ControlFrame = ({ customPrepend, enableActions, embeddableId }: Con } = useReduxContainerContext(); const { controlStyle } = useEmbeddableSelector((state) => state); - // Presentation Services Context + // Controls Services Context const { overlays } = pluginServices.getHooks(); const { openConfirm } = overlays.useService(); diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_component.tsx b/src/plugins/controls/public/control_group/component/control_group_component.tsx similarity index 96% rename from src/plugins/presentation_util/public/components/controls/control_group/component/control_group_component.tsx rename to src/plugins/controls/public/control_group/component/control_group_component.tsx index 026f3154fe50e..a28807a36eb49 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_component.tsx +++ b/src/plugins/controls/public/control_group/component/control_group_component.tsx @@ -37,18 +37,18 @@ import { } from '@dnd-kit/core'; import { ControlGroupInput } from '../types'; -import { pluginServices } from '../../../../services'; +import { pluginServices } from '../../services'; +import { ViewMode } from '../../../../embeddable/public'; import { ControlGroupStrings } from '../control_group_strings'; import { CreateControlButton } from '../editor/create_control'; -import { ViewMode } from '../../../../../../embeddable/public'; import { EditControlGroup } from '../editor/edit_control_group'; import { forwardAllContext } from '../editor/forward_all_context'; import { controlGroupReducers } from '../state/control_group_reducers'; import { ControlClone, SortableControl } from './control_group_sortable_item'; -import { useReduxContainerContext } from '../../../redux_embeddables/redux_embeddable_context'; +import { useReduxContainerContext } from '../../../../presentation_util/public'; export const ControlGroup = () => { - // Presentation Services Context + // Controls Services Context const { overlays } = pluginServices.getHooks(); const { openFlyout } = overlays.useService(); diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx b/src/plugins/controls/public/control_group/component/control_group_sortable_item.tsx similarity index 98% rename from src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx rename to src/plugins/controls/public/control_group/component/control_group_sortable_item.tsx index f4c28e840556a..ecba29bb5f1db 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx +++ b/src/plugins/controls/public/control_group/component/control_group_sortable_item.tsx @@ -14,7 +14,7 @@ import classNames from 'classnames'; import { ControlGroupInput } from '../types'; import { ControlFrame, ControlFrameProps } from './control_frame_component'; -import { useReduxContainerContext } from '../../../redux_embeddables/redux_embeddable_context'; +import { useReduxContainerContext } from '../../../../presentation_util/public'; interface DragInfo { isOver?: boolean; diff --git a/src/plugins/presentation_util/public/components/controls/control_group/control_group.scss b/src/plugins/controls/public/control_group/control_group.scss similarity index 100% rename from src/plugins/presentation_util/public/components/controls/control_group/control_group.scss rename to src/plugins/controls/public/control_group/control_group.scss diff --git a/src/plugins/controls/public/control_group/control_group_strings.ts b/src/plugins/controls/public/control_group/control_group_strings.ts new file mode 100644 index 0000000000000..91e857d083f7f --- /dev/null +++ b/src/plugins/controls/public/control_group/control_group_strings.ts @@ -0,0 +1,194 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ControlGroupStrings = { + getEmbeddableTitle: () => + i18n.translate('controls.controlGroup.title', { + defaultMessage: 'Control group', + }), + emptyState: { + getCallToAction: () => + i18n.translate('controls.controlGroup.emptyState.callToAction', { + defaultMessage: 'Controls let you filter and interact with your dashboard data', + }), + getAddControlButtonTitle: () => + i18n.translate('controls.controlGroup.emptyState.addControlButtonTitle', { + defaultMessage: 'Add control', + }), + getTwoLineLoadingTitle: () => + i18n.translate('controls.controlGroup.emptyState.twoLineLoadingTitle', { + defaultMessage: '...', + }), + }, + manageControl: { + getFlyoutCreateTitle: () => + i18n.translate('controls.controlGroup.manageControl.createFlyoutTitle', { + defaultMessage: 'Create control', + }), + getFlyoutEditTitle: () => + i18n.translate('controls.controlGroup.manageControl.editFlyoutTitle', { + defaultMessage: 'Edit control', + }), + getTitleInputTitle: () => + i18n.translate('controls.controlGroup.manageControl.titleInputTitle', { + defaultMessage: 'Title', + }), + getWidthInputTitle: () => + i18n.translate('controls.controlGroup.manageControl.widthInputTitle', { + defaultMessage: 'Control size', + }), + getSaveChangesTitle: () => + i18n.translate('controls.controlGroup.manageControl.saveChangesTitle', { + defaultMessage: 'Save and close', + }), + getCancelTitle: () => + i18n.translate('controls.controlGroup.manageControl.cancelTitle', { + defaultMessage: 'Cancel', + }), + }, + management: { + getAddControlTitle: () => + i18n.translate('controls.controlGroup.management.addControl', { + defaultMessage: 'Add control', + }), + getManageButtonTitle: () => + i18n.translate('controls.controlGroup.management.buttonTitle', { + defaultMessage: 'Configure controls', + }), + getFlyoutTitle: () => + i18n.translate('controls.controlGroup.management.flyoutTitle', { + defaultMessage: 'Configure controls', + }), + getDefaultWidthTitle: () => + i18n.translate('controls.controlGroup.management.defaultWidthTitle', { + defaultMessage: 'Default size', + }), + getLayoutTitle: () => + i18n.translate('controls.controlGroup.management.layoutTitle', { + defaultMessage: 'Layout', + }), + getDeleteButtonTitle: () => + i18n.translate('controls.controlGroup.management.delete', { + defaultMessage: 'Delete control', + }), + getSetAllWidthsToDefaultTitle: () => + i18n.translate('controls.controlGroup.management.setAllWidths', { + defaultMessage: 'Set all sizes to default', + }), + getDeleteAllButtonTitle: () => + i18n.translate('controls.controlGroup.management.deleteAll', { + defaultMessage: 'Delete all', + }), + controlWidth: { + getWidthSwitchLegend: () => + i18n.translate('controls.controlGroup.management.layout.controlWidthLegend', { + defaultMessage: 'Change control size', + }), + getAutoWidthTitle: () => + i18n.translate('controls.controlGroup.management.layout.auto', { + defaultMessage: 'Auto', + }), + getSmallWidthTitle: () => + i18n.translate('controls.controlGroup.management.layout.small', { + defaultMessage: 'Small', + }), + getMediumWidthTitle: () => + i18n.translate('controls.controlGroup.management.layout.medium', { + defaultMessage: 'Medium', + }), + getLargeWidthTitle: () => + i18n.translate('controls.controlGroup.management.layout.large', { + defaultMessage: 'Large', + }), + }, + controlStyle: { + getDesignSwitchLegend: () => + i18n.translate('controls.controlGroup.management.layout.designSwitchLegend', { + defaultMessage: 'Switch control designs', + }), + getSingleLineTitle: () => + i18n.translate('controls.controlGroup.management.layout.singleLine', { + defaultMessage: 'Single line', + }), + getTwoLineTitle: () => + i18n.translate('controls.controlGroup.management.layout.twoLine', { + defaultMessage: 'Double line', + }), + }, + deleteControls: { + getDeleteAllTitle: () => + i18n.translate('controls.controlGroup.management.delete.deleteAllTitle', { + defaultMessage: 'Delete all controls?', + }), + getDeleteTitle: () => + i18n.translate('controls.controlGroup.management.delete.deleteTitle', { + defaultMessage: 'Delete control?', + }), + getSubtitle: () => + i18n.translate('controls.controlGroup.management.delete.sub', { + defaultMessage: 'Controls are not recoverable once removed.', + }), + getConfirm: () => + i18n.translate('controls.controlGroup.management.delete.confirm', { + defaultMessage: 'Delete', + }), + getCancel: () => + i18n.translate('controls.controlGroup.management.delete.cancel', { + defaultMessage: 'Cancel', + }), + }, + discardChanges: { + getTitle: () => + i18n.translate('controls.controlGroup.management.discard.title', { + defaultMessage: 'Discard changes?', + }), + getSubtitle: () => + i18n.translate('controls.controlGroup.management.discard.sub', { + defaultMessage: `Changes that you've made to this control will be discarded, are you sure you want to continue?`, + }), + getConfirm: () => + i18n.translate('controls.controlGroup.management.discard.confirm', { + defaultMessage: 'Discard changes', + }), + getCancel: () => + i18n.translate('controls.controlGroup.management.discard.cancel', { + defaultMessage: 'Cancel', + }), + }, + discardNewControl: { + getTitle: () => + i18n.translate('controls.controlGroup.management.deleteNew.title', { + defaultMessage: 'Discard new control', + }), + getSubtitle: () => + i18n.translate('controls.controlGroup.management.deleteNew.sub', { + defaultMessage: `Changes that you've made to this control will be discarded, are you sure you want to continue?`, + }), + getConfirm: () => + i18n.translate('controls.controlGroup.management.deleteNew.confirm', { + defaultMessage: 'Discard control', + }), + getCancel: () => + i18n.translate('controls.controlGroup.management.deleteNew.cancel', { + defaultMessage: 'Cancel', + }), + }, + }, + floatingActions: { + getEditButtonTitle: () => + i18n.translate('controls.controlGroup.floatingActions.editTitle', { + defaultMessage: 'Edit control', + }), + getRemoveButtonTitle: () => + i18n.translate('controls.controlGroup.floatingActions.removeTitle', { + defaultMessage: 'Remove control', + }), + }, +}; diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/control_editor.tsx b/src/plugins/controls/public/control_group/editor/control_editor.tsx similarity index 100% rename from src/plugins/presentation_util/public/components/controls/control_group/editor/control_editor.tsx rename to src/plugins/controls/public/control_group/editor/control_editor.tsx diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/create_control.tsx b/src/plugins/controls/public/control_group/editor/create_control.tsx similarity index 94% rename from src/plugins/presentation_util/public/components/controls/control_group/editor/create_control.tsx rename to src/plugins/controls/public/control_group/editor/create_control.tsx index de7e38400f6bc..b97ebb9aa519b 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/editor/create_control.tsx +++ b/src/plugins/controls/public/control_group/editor/create_control.tsx @@ -17,19 +17,19 @@ import { import React, { useState, ReactElement } from 'react'; import { ControlGroupInput } from '../types'; +import { pluginServices } from '../../services'; import { ControlEditor } from './control_editor'; -import { pluginServices } from '../../../../services'; +import { OverlayRef } from '../../../../../core/public'; import { forwardAllContext } from './forward_all_context'; import { DEFAULT_CONTROL_WIDTH } from './editor_constants'; -import { OverlayRef } from '../../../../../../../core/public'; import { ControlGroupStrings } from '../control_group_strings'; import { controlGroupReducers } from '../state/control_group_reducers'; +import { EmbeddableFactoryNotFoundError } from '../../../../embeddable/public'; +import { useReduxContainerContext } from '../../../../presentation_util/public'; import { ControlWidth, IEditableControlFactory, ControlInput } from '../../types'; -import { EmbeddableFactoryNotFoundError } from '../../../../../../embeddable/public'; -import { useReduxContainerContext } from '../../../redux_embeddables/redux_embeddable_context'; export const CreateControlButton = ({ isIconButton }: { isIconButton: boolean }) => { - // Presentation Services Context + // Controls Services Context const { overlays, controls } = pluginServices.getHooks(); const { getControlTypes, getControlFactory } = controls.useService(); const { openFlyout, openConfirm } = overlays.useService(); diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx b/src/plugins/controls/public/control_group/editor/edit_control.tsx similarity index 93% rename from src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx rename to src/plugins/controls/public/control_group/editor/edit_control.tsx index eb628049f7c93..210000e4f617c 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx +++ b/src/plugins/controls/public/control_group/editor/edit_control.tsx @@ -12,17 +12,17 @@ import React, { useEffect, useRef } from 'react'; import { ControlGroupInput } from '../types'; import { ControlEditor } from './control_editor'; -import { pluginServices } from '../../../../services'; +import { pluginServices } from '../../services'; import { forwardAllContext } from './forward_all_context'; -import { OverlayRef } from '../../../../../../../core/public'; +import { OverlayRef } from '../../../../../core/public'; import { ControlGroupStrings } from '../control_group_strings'; import { IEditableControlFactory, ControlInput } from '../../types'; import { controlGroupReducers } from '../state/control_group_reducers'; -import { EmbeddableFactoryNotFoundError } from '../../../../../../embeddable/public'; -import { useReduxContainerContext } from '../../../redux_embeddables/redux_embeddable_context'; +import { EmbeddableFactoryNotFoundError } from '../../../../embeddable/public'; +import { useReduxContainerContext } from '../../../../presentation_util/public'; export const EditControlButton = ({ embeddableId }: { embeddableId: string }) => { - // Presentation Services Context + // Controls Services Context const { overlays, controls } = pluginServices.getHooks(); const { getControlFactory } = controls.useService(); const { openFlyout, openConfirm } = overlays.useService(); diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control_group.tsx b/src/plugins/controls/public/control_group/editor/edit_control_group.tsx similarity index 97% rename from src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control_group.tsx rename to src/plugins/controls/public/control_group/editor/edit_control_group.tsx index 9828f6317ad53..87a2a1407a761 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control_group.tsx +++ b/src/plugins/controls/public/control_group/editor/edit_control_group.tsx @@ -28,11 +28,11 @@ import { DEFAULT_CONTROL_WIDTH, } from './editor_constants'; import { ControlGroupInput } from '../types'; -import { pluginServices } from '../../../../services'; +import { pluginServices } from '../../services'; import { ControlStyle, ControlWidth } from '../../types'; import { ControlGroupStrings } from '../control_group_strings'; import { controlGroupReducers } from '../state/control_group_reducers'; -import { useReduxContainerContext } from '../../../redux_embeddables/redux_embeddable_context'; +import { useReduxContainerContext } from '../../../../presentation_util/public'; interface EditControlGroupState { newControlStyle: ControlGroupInput['controlStyle']; diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts b/src/plugins/controls/public/control_group/editor/editor_constants.ts similarity index 100% rename from src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts rename to src/plugins/controls/public/control_group/editor/editor_constants.ts diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/forward_all_context.tsx b/src/plugins/controls/public/control_group/editor/forward_all_context.tsx similarity index 67% rename from src/plugins/presentation_util/public/components/controls/control_group/editor/forward_all_context.tsx rename to src/plugins/controls/public/control_group/editor/forward_all_context.tsx index bb7356c240648..23d7e60573323 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/editor/forward_all_context.tsx +++ b/src/plugins/controls/public/control_group/editor/forward_all_context.tsx @@ -6,16 +6,16 @@ * Side Public License, v 1. */ -import { Provider } from 'react-redux'; import { ReactElement } from 'react'; import React from 'react'; import { ControlGroupInput } from '../types'; -import { pluginServices } from '../../../../services'; -import { toMountPoint } from '../../../../../../kibana_react/public'; -import { ReduxContainerContextServices } from '../../../redux_embeddables/types'; -import { ReduxEmbeddableContext } from '../../../redux_embeddables/redux_embeddable_context'; -import { getManagedEmbeddablesStore } from '../../../redux_embeddables/generic_embeddable_store'; +import { pluginServices } from '../../services'; +import { toMountPoint } from '../../../../kibana_react/public'; +import { + ReduxContainerContextServices, + ReduxEmbeddableContext, +} from '../../../../presentation_util/public'; /** * The overlays service creates its divs outside the flow of the component. This necessitates @@ -26,11 +26,12 @@ export const forwardAllContext = ( reduxContainerContext: ReduxContainerContextServices ) => { const PresentationUtilProvider = pluginServices.getContextProvider(); + const StoreProvider = reduxContainerContext.ReduxEmbeddableStoreProvider; return toMountPoint( - + {component} - + ); }; diff --git a/src/plugins/presentation_util/public/components/controls/control_group/embeddable/control_group_container.tsx b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx similarity index 89% rename from src/plugins/presentation_util/public/components/controls/control_group/embeddable/control_group_container.tsx rename to src/plugins/controls/public/control_group/embeddable/control_group_container.tsx index ff25286a75211..65c93e42a472f 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/embeddable/control_group_container.tsx +++ b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx @@ -27,14 +27,22 @@ import { ControlPanelState, CONTROL_GROUP_TYPE, } from '../types'; -import { pluginServices } from '../../../../services'; -import { DataView } from '../../../../../../data_views/public'; +import { + withSuspense, + LazyReduxEmbeddableWrapper, + ReduxEmbeddableWrapperPropsWithChildren, +} from '../../../../presentation_util/public'; +import { pluginServices } from '../../services'; +import { DataView } from '../../../../data_views/public'; +import { DEFAULT_CONTROL_WIDTH } from '../editor/editor_constants'; import { ControlGroup } from '../component/control_group_component'; import { controlGroupReducers } from '../state/control_group_reducers'; import { ControlEmbeddable, ControlInput, ControlOutput } from '../../types'; -import { Container, EmbeddableFactory } from '../../../../../../embeddable/public'; -import { ReduxEmbeddableWrapper } from '../../../redux_embeddables/redux_embeddable_wrapper'; -import { DEFAULT_CONTROL_WIDTH } from '../editor/editor_constants'; +import { Container, EmbeddableFactory } from '../../../../embeddable/public'; + +const ControlGroupReduxWrapper = withSuspense< + ReduxEmbeddableWrapperPropsWithChildren +>(LazyReduxEmbeddableWrapper); export class ControlGroupContainer extends Container< ControlInput, @@ -145,12 +153,9 @@ export class ControlGroupContainer extends Container< const PresentationUtilProvider = pluginServices.getContextProvider(); ReactDOM.render( - - embeddable={this} - reducers={controlGroupReducers} - > + - + , dom ); diff --git a/src/plugins/presentation_util/public/components/controls/control_group/embeddable/control_group_container_factory.ts b/src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts similarity index 91% rename from src/plugins/presentation_util/public/components/controls/control_group/embeddable/control_group_container_factory.ts rename to src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts index 5a71355da8bbe..d2e057a613070 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/embeddable/control_group_container_factory.ts +++ b/src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts @@ -14,14 +14,15 @@ * Side Public License, v 1. */ -import { Container, EmbeddableFactoryDefinition } from '../../../../../../embeddable/public'; -import { EmbeddablePersistableStateService } from '../../../../../../embeddable/common'; +import { Container, EmbeddableFactoryDefinition } from '../../../../embeddable/public'; +import { EmbeddablePersistableStateService } from '../../../../embeddable/common'; + import { ControlGroupInput, CONTROL_GROUP_TYPE } from '../types'; import { ControlGroupStrings } from '../control_group_strings'; import { createControlGroupExtract, createControlGroupInject, -} from '../../../../../common/controls/control_group/control_group_persistable_state'; +} from '../../../common/control_group/control_group_persistable_state'; export class ControlGroupContainerFactory implements EmbeddableFactoryDefinition { public readonly isContainerType = true; diff --git a/src/plugins/presentation_util/public/components/controls/control_group/index.ts b/src/plugins/controls/public/control_group/index.ts similarity index 92% rename from src/plugins/presentation_util/public/components/controls/control_group/index.ts rename to src/plugins/controls/public/control_group/index.ts index 95988d2e8143c..60050786d7c11 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/index.ts +++ b/src/plugins/controls/public/control_group/index.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ -export type { ControlGroupInput, ControlGroupOutput } from './types'; export type { ControlGroupContainer } from './embeddable/control_group_container'; +export type { ControlGroupInput, ControlGroupOutput } from './types'; + +export { CONTROL_GROUP_TYPE } from './types'; export { ControlGroupContainerFactory } from './embeddable/control_group_container_factory'; diff --git a/src/plugins/presentation_util/public/components/controls/control_group/opt_a.svg b/src/plugins/controls/public/control_group/opt_a.svg similarity index 100% rename from src/plugins/presentation_util/public/components/controls/control_group/opt_a.svg rename to src/plugins/controls/public/control_group/opt_a.svg diff --git a/src/plugins/presentation_util/public/components/controls/control_group/state/control_group_reducers.ts b/src/plugins/controls/public/control_group/state/control_group_reducers.ts similarity index 100% rename from src/plugins/presentation_util/public/components/controls/control_group/state/control_group_reducers.ts rename to src/plugins/controls/public/control_group/state/control_group_reducers.ts diff --git a/src/plugins/presentation_util/public/components/controls/control_group/types.ts b/src/plugins/controls/public/control_group/types.ts similarity index 69% rename from src/plugins/presentation_util/public/components/controls/control_group/types.ts rename to src/plugins/controls/public/control_group/types.ts index 3d0123eb4192f..5f6ec00efffab 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/types.ts +++ b/src/plugins/controls/public/control_group/types.ts @@ -7,8 +7,13 @@ */ import { CommonControlOutput } from '../types'; -import { ContainerOutput } from '../../../../../embeddable/public'; +import { ContainerOutput } from '../../../embeddable/public'; export type ControlGroupOutput = ContainerOutput & CommonControlOutput; -export * from '../../../../common/controls/control_group/types'; +export { + type ControlsPanels, + type ControlGroupInput, + type ControlPanelState, + CONTROL_GROUP_TYPE, +} from '../../common/control_group/types'; diff --git a/src/plugins/presentation_util/public/components/controls/control_types/index.ts b/src/plugins/controls/public/control_types/index.ts similarity index 100% rename from src/plugins/presentation_util/public/components/controls/control_types/index.ts rename to src/plugins/controls/public/control_types/index.ts diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/index.ts b/src/plugins/controls/public/control_types/options_list/index.ts similarity index 72% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/index.ts rename to src/plugins/controls/public/control_types/options_list/index.ts index f2d9c29701a5f..7a254bf423ce4 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/index.ts +++ b/src/plugins/controls/public/control_types/options_list/index.ts @@ -6,5 +6,8 @@ * Side Public License, v 1. */ +export { OPTIONS_LIST_CONTROL } from '../../../common/control_types/options_list/types'; export { OptionsListEmbeddableFactory } from './options_list_embeddable_factory'; + export type { OptionsListEmbeddable } from './options_list_embeddable'; +export type { OptionsListEmbeddableInput } from '../../../common/control_types/options_list/types'; diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list.scss b/src/plugins/controls/public/control_types/options_list/options_list.scss similarity index 100% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list.scss rename to src/plugins/controls/public/control_types/options_list/options_list.scss diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx b/src/plugins/controls/public/control_types/options_list/options_list_component.tsx similarity index 97% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx rename to src/plugins/controls/public/control_types/options_list/options_list_component.tsx index 1c79d1ce3e9b0..74f2130528e97 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx +++ b/src/plugins/controls/public/control_types/options_list/options_list_component.tsx @@ -15,7 +15,7 @@ import { debounce } from 'lodash'; import { OptionsListStrings } from './options_list_strings'; import { optionsListReducers } from './options_list_reducers'; import { OptionsListPopover } from './options_list_popover_component'; -import { useReduxEmbeddableContext } from '../../../redux_embeddables/redux_embeddable_context'; +import { useReduxEmbeddableContext } from '../../../../presentation_util/public'; import './options_list.scss'; import { useStateObservable } from '../../hooks/use_state_observable'; diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_editor.tsx b/src/plugins/controls/public/control_types/options_list/options_list_editor.tsx similarity index 90% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_editor.tsx rename to src/plugins/controls/public/control_types/options_list/options_list_editor.tsx index 86f4f85b3b0b2..d9231a5d8b2e5 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_editor.tsx +++ b/src/plugins/controls/public/control_types/options_list/options_list_editor.tsx @@ -10,13 +10,16 @@ import useMount from 'react-use/lib/useMount'; import React, { useEffect, useState } from 'react'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { pluginServices } from '../../services'; import { ControlEditorProps } from '../../types'; -import { DataViewListItem, DataView } from '../../../../../../data_views/common'; -import { DataViewPicker } from '../../../data_view_picker/data_view_picker'; -import { OptionsListStrings } from './options_list_strings'; -import { pluginServices } from '../../../../services'; import { OptionsListEmbeddableInput } from './types'; -import { FieldPicker } from '../../../field_picker/field_picker'; +import { OptionsListStrings } from './options_list_strings'; +import { DataViewListItem, DataView } from '../../../../data_views/common'; +import { + LazyDataViewPicker, + LazyFieldPicker, + withSuspense, +} from '../../../../presentation_util/public'; interface OptionsListEditorState { singleSelect?: boolean; @@ -27,13 +30,16 @@ interface OptionsListEditorState { fieldName?: string; } +const FieldPicker = withSuspense(LazyFieldPicker, null); +const DataViewPicker = withSuspense(LazyDataViewPicker, null); + export const OptionsListEditor = ({ onChange, initialInput, setValidState, setDefaultTitle, }: ControlEditorProps) => { - // Presentation Services Context + // Controls Services Context const { dataViews } = pluginServices.getHooks(); const { getIdsWithTitle, getDefaultId, get } = dataViews.useService(); diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_embeddable.tsx b/src/plugins/controls/public/control_types/options_list/options_list_embeddable.tsx similarity index 89% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_embeddable.tsx rename to src/plugins/controls/public/control_types/options_list/options_list_embeddable.tsx index b980ee10293e5..ce570fcbf769e 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_embeddable.tsx +++ b/src/plugins/controls/public/control_types/options_list/options_list_embeddable.tsx @@ -6,31 +6,39 @@ * Side Public License, v 1. */ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { isEqual } from 'lodash'; -import deepEqual from 'fast-deep-equal'; import { + Filter, buildEsQuery, + compareFilters, buildPhraseFilter, buildPhrasesFilter, - compareFilters, - Filter, } from '@kbn/es-query'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { isEqual } from 'lodash'; +import deepEqual from 'fast-deep-equal'; import { merge, Subject, Subscription, BehaviorSubject } from 'rxjs'; import { tap, debounceTime, map, distinctUntilChanged, skip } from 'rxjs/operators'; -import { ReduxEmbeddableWrapper } from '../../../redux_embeddables/redux_embeddable_wrapper'; import { OptionsListComponent, OptionsListComponentState } from './options_list_component'; -import { PresentationDataViewsService } from '../../../../services/data_views'; -import { Embeddable, IContainer } from '../../../../../../embeddable/public'; +import { + withSuspense, + LazyReduxEmbeddableWrapper, + ReduxEmbeddableWrapperPropsWithChildren, +} from '../../../../presentation_util/public'; import { OptionsListEmbeddableInput, OPTIONS_LIST_CONTROL } from './types'; -import { PresentationDataService } from '../../../../services/data'; -import { DataView } from '../../../../../../data_views/public'; +import { ControlsDataViewsService } from '../../services/data_views'; +import { Embeddable, IContainer } from '../../../../embeddable/public'; +import { ControlsDataService } from '../../services/data'; import { optionsListReducers } from './options_list_reducers'; import { OptionsListStrings } from './options_list_strings'; -import { pluginServices } from '../../../../services'; +import { DataView } from '../../../../data_views/public'; import { ControlInput, ControlOutput } from '../..'; +import { pluginServices } from '../../services'; + +const OptionsListReduxWrapper = withSuspense< + ReduxEmbeddableWrapperPropsWithChildren +>(LazyReduxEmbeddableWrapper); const diffDataFetchProps = ( current?: OptionsListDataFetchProps, @@ -62,9 +70,9 @@ export class OptionsListEmbeddable extends Embeddable = new Subject(); @@ -80,7 +88,7 @@ export class OptionsListEmbeddable extends Embeddable - embeddable={this} - reducers={optionsListReducers} - > + - , + , node ); }; diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_embeddable_factory.tsx b/src/plugins/controls/public/control_types/options_list/options_list_embeddable_factory.tsx similarity index 93% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_embeddable_factory.tsx rename to src/plugins/controls/public/control_types/options_list/options_list_embeddable_factory.tsx index cb53ac463be3f..98d616faadc58 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_embeddable_factory.tsx +++ b/src/plugins/controls/public/control_types/options_list/options_list_embeddable_factory.tsx @@ -11,11 +11,11 @@ import deepEqual from 'fast-deep-equal'; import { OptionsListEditor } from './options_list_editor'; import { ControlEmbeddable, IEditableControlFactory } from '../../types'; import { OptionsListEmbeddableInput, OPTIONS_LIST_CONTROL } from './types'; -import { EmbeddableFactoryDefinition, IContainer } from '../../../../../../embeddable/public'; +import { EmbeddableFactoryDefinition, IContainer } from '../../../../embeddable/public'; import { createOptionsListExtract, createOptionsListInject, -} from '../../../../../common/controls/control_types/options_list/options_list_persistable_state'; +} from '../../../common/control_types/options_list/options_list_persistable_state'; export class OptionsListEmbeddableFactory implements EmbeddableFactoryDefinition, IEditableControlFactory diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx b/src/plugins/controls/public/control_types/options_list/options_list_popover_component.tsx similarity index 98% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx rename to src/plugins/controls/public/control_types/options_list/options_list_popover_component.tsx index 4aae049a5d446..0634d676f57a2 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx +++ b/src/plugins/controls/public/control_types/options_list/options_list_popover_component.tsx @@ -24,7 +24,7 @@ import { OptionsListEmbeddableInput } from './types'; import { OptionsListStrings } from './options_list_strings'; import { optionsListReducers } from './options_list_reducers'; import { OptionsListComponentState } from './options_list_component'; -import { useReduxEmbeddableContext } from '../../../redux_embeddables/redux_embeddable_context'; +import { useReduxEmbeddableContext } from '../../../../presentation_util/public'; export const OptionsListPopover = ({ loading, diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_reducers.ts b/src/plugins/controls/public/control_types/options_list/options_list_reducers.ts similarity index 100% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_reducers.ts rename to src/plugins/controls/public/control_types/options_list/options_list_reducers.ts diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_strings.ts b/src/plugins/controls/public/control_types/options_list/options_list_strings.ts similarity index 59% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_strings.ts rename to src/plugins/controls/public/control_types/options_list/options_list_strings.ts index dee0d4e7e1807..0a6e46c514d11 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_strings.ts +++ b/src/plugins/controls/public/control_types/options_list/options_list_strings.ts @@ -11,65 +11,65 @@ import { i18n } from '@kbn/i18n'; export const OptionsListStrings = { summary: { getSeparator: () => - i18n.translate('presentationUtil.controls.optionsList.summary.separator', { + i18n.translate('controls.optionsList.summary.separator', { defaultMessage: ', ', }), getPlaceholder: () => - i18n.translate('presentationUtil.controls.optionsList.summary.placeholder', { + i18n.translate('controls.optionsList.summary.placeholder', { defaultMessage: 'Select...', }), }, editor: { getIndexPatternTitle: () => - i18n.translate('presentationUtil.controls.optionsList.editor.indexPatternTitle', { + i18n.translate('controls.optionsList.editor.indexPatternTitle', { defaultMessage: 'Index pattern', }), getDataViewTitle: () => - i18n.translate('presentationUtil.controls.optionsList.editor.dataViewTitle', { + i18n.translate('controls.optionsList.editor.dataViewTitle', { defaultMessage: 'Data view', }), getNoDataViewTitle: () => - i18n.translate('presentationUtil.controls.optionsList.editor.noDataViewTitle', { + i18n.translate('controls.optionsList.editor.noDataViewTitle', { defaultMessage: 'Select data view', }), getFieldTitle: () => - i18n.translate('presentationUtil.controls.optionsList.editor.fieldTitle', { + i18n.translate('controls.optionsList.editor.fieldTitle', { defaultMessage: 'Field', }), getAllowMultiselectTitle: () => - i18n.translate('presentationUtil.inputControls.optionsList.editor.allowMultiselectTitle', { + i18n.translate('controls.optionsList.editor.allowMultiselectTitle', { defaultMessage: 'Allow multiple selections in dropdown', }), }, popover: { getLoadingMessage: () => - i18n.translate('presentationUtil.controls.optionsList.popover.loading', { + i18n.translate('controls.optionsList.popover.loading', { defaultMessage: 'Loading filters', }), getEmptyMessage: () => - i18n.translate('presentationUtil.controls.optionsList.popover.empty', { + i18n.translate('controls.optionsList.popover.empty', { defaultMessage: 'No filters found', }), getSelectionsEmptyMessage: () => - i18n.translate('presentationUtil.controls.optionsList.popover.selectionsEmpty', { + i18n.translate('controls.optionsList.popover.selectionsEmpty', { defaultMessage: 'You have no selections', }), getAllOptionsButtonTitle: () => - i18n.translate('presentationUtil.controls.optionsList.popover.allOptionsTitle', { + i18n.translate('controls.optionsList.popover.allOptionsTitle', { defaultMessage: 'Show all options', }), getSelectedOptionsButtonTitle: () => - i18n.translate('presentationUtil.inputControls.optionsList.popover.selectedOptionsTitle', { + i18n.translate('controls.optionsList.popover.selectedOptionsTitle', { defaultMessage: 'Show only selected options', }), getClearAllSelectionsButtonTitle: () => - i18n.translate('presentationUtil.controls.optionsList.popover.clearAllSelectionsTitle', { + i18n.translate('controls.optionsList.popover.clearAllSelectionsTitle', { defaultMessage: 'Clear selections', }), }, errors: { getDataViewNotFoundError: (dataViewId: string) => - i18n.translate('presentationUtil.controls.optionsList.errors.dataViewNotFound', { + i18n.translate('controls.optionsList.errors.dataViewNotFound', { defaultMessage: 'Could not locate data view: {dataViewId}', values: { dataViewId }, }), diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/types.ts b/src/plugins/controls/public/control_types/options_list/types.ts similarity index 81% rename from src/plugins/presentation_util/public/components/controls/control_types/options_list/types.ts rename to src/plugins/controls/public/control_types/options_list/types.ts index 06b6526f38db4..f537cccf3d690 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/types.ts +++ b/src/plugins/controls/public/control_types/options_list/types.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export * from '../../../../../common/controls/control_types/options_list/types'; +export * from '../../../common/control_types/options_list/types'; diff --git a/src/plugins/presentation_util/public/components/controls/controls_service.ts b/src/plugins/controls/public/controls_service.ts similarity index 88% rename from src/plugins/presentation_util/public/components/controls/controls_service.ts rename to src/plugins/controls/public/controls_service.ts index 436d36fcc9db0..69efacbef2915 100644 --- a/src/plugins/presentation_util/public/components/controls/controls_service.ts +++ b/src/plugins/controls/public/controls_service.ts @@ -7,8 +7,8 @@ */ import { ControlEmbeddable, ControlFactory, ControlInput, ControlOutput } from '.'; -import { EmbeddableFactory } from '../../../../embeddable/public'; -import { ControlTypeRegistry } from '../../services/controls'; +import { EmbeddableFactory } from '../../embeddable/public'; +import { ControlTypeRegistry } from './services/controls'; export class ControlsService { private controlsFactoriesMap: ControlTypeRegistry = {}; diff --git a/src/plugins/presentation_util/public/components/controls/hooks/use_child_embeddable.ts b/src/plugins/controls/public/hooks/use_child_embeddable.ts similarity index 100% rename from src/plugins/presentation_util/public/components/controls/hooks/use_child_embeddable.ts rename to src/plugins/controls/public/hooks/use_child_embeddable.ts diff --git a/src/plugins/presentation_util/public/components/controls/hooks/use_state_observable.ts b/src/plugins/controls/public/hooks/use_state_observable.ts similarity index 100% rename from src/plugins/presentation_util/public/components/controls/hooks/use_state_observable.ts rename to src/plugins/controls/public/hooks/use_state_observable.ts diff --git a/src/plugins/controls/public/index.ts b/src/plugins/controls/public/index.ts new file mode 100644 index 0000000000000..c8118fcdb1d77 --- /dev/null +++ b/src/plugins/controls/public/index.ts @@ -0,0 +1,44 @@ +/* + * 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 { ControlsPlugin } from './plugin'; + +export type { + ControlOutput, + ControlFactory, + ControlEmbeddable, + ControlEditorProps, + CommonControlOutput, + IEditableControlFactory, +} from './types'; + +export type { + ControlWidth, + ControlStyle, + ParentIgnoreSettings, + ControlInput, +} from '../common/types'; + +export { OPTIONS_LIST_CONTROL, CONTROL_GROUP_TYPE } from '../common'; + +export { + ControlGroupContainer, + ControlGroupContainerFactory, + type ControlGroupInput, + type ControlGroupOutput, +} from './control_group'; + +export { + OptionsListEmbeddableFactory, + OptionsListEmbeddable, + type OptionsListEmbeddableInput, +} from './control_types'; + +export function plugin() { + return new ControlsPlugin(); +} diff --git a/src/plugins/controls/public/plugin.ts b/src/plugins/controls/public/plugin.ts new file mode 100644 index 0000000000000..c4ff865a05e47 --- /dev/null +++ b/src/plugins/controls/public/plugin.ts @@ -0,0 +1,95 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { pluginServices } from './services'; +import { registry } from './services/kibana'; +import { + ControlsPluginSetup, + ControlsPluginStart, + ControlsPluginSetupDeps, + ControlsPluginStartDeps, + IEditableControlFactory, + ControlEditorProps, + ControlEmbeddable, + ControlInput, +} from './types'; +import { OptionsListEmbeddableFactory } from './control_types/options_list'; +import { ControlGroupContainerFactory, CONTROL_GROUP_TYPE, OPTIONS_LIST_CONTROL } from '.'; + +export class ControlsPlugin + implements + Plugin< + ControlsPluginSetup, + ControlsPluginStart, + ControlsPluginSetupDeps, + ControlsPluginStartDeps + > +{ + private inlineEditors: { + [key: string]: { + controlEditorComponent?: (props: ControlEditorProps) => JSX.Element; + presaveTransformFunction?: ( + newInput: Partial, + embeddable?: ControlEmbeddable + ) => Partial; + }; + } = {}; + + public setup( + _coreSetup: CoreSetup, + _setupPlugins: ControlsPluginSetupDeps + ): ControlsPluginSetup { + _coreSetup.getStartServices().then(([coreStart, deps]) => { + // register control group embeddable factory + embeddable.registerEmbeddableFactory( + CONTROL_GROUP_TYPE, + new ControlGroupContainerFactory(deps.embeddable) + ); + }); + + const { embeddable } = _setupPlugins; + + // create control type embeddable factories. + const optionsListFactory = new OptionsListEmbeddableFactory(); + const editableOptionsListFactory = optionsListFactory as IEditableControlFactory; + this.inlineEditors[OPTIONS_LIST_CONTROL] = { + controlEditorComponent: editableOptionsListFactory.controlEditorComponent, + presaveTransformFunction: editableOptionsListFactory.presaveTransformFunction, + }; + embeddable.registerEmbeddableFactory(OPTIONS_LIST_CONTROL, optionsListFactory); + + return {}; + } + + public start(coreStart: CoreStart, startPlugins: ControlsPluginStartDeps): ControlsPluginStart { + pluginServices.setRegistry(registry.start({ coreStart, startPlugins })); + const { controls: controlsService } = pluginServices.getServices(); + const { embeddable } = startPlugins; + + // register control types with controls service. + const optionsListFactory = embeddable.getEmbeddableFactory(OPTIONS_LIST_CONTROL); + // Temporarily pass along inline editors - inline editing should be made a first-class feature of embeddables + const editableOptionsListFactory = optionsListFactory as IEditableControlFactory; + const { + controlEditorComponent: optionsListControlEditor, + presaveTransformFunction: optionsListPresaveTransform, + } = this.inlineEditors[OPTIONS_LIST_CONTROL]; + editableOptionsListFactory.controlEditorComponent = optionsListControlEditor; + editableOptionsListFactory.presaveTransformFunction = optionsListPresaveTransform; + + if (optionsListFactory) controlsService.registerControlType(optionsListFactory); + + return { + ContextProvider: pluginServices.getContextProvider(), + controlsService, + }; + } + + public stop() {} +} diff --git a/src/plugins/presentation_util/public/services/controls.ts b/src/plugins/controls/public/services/controls.ts similarity index 89% rename from src/plugins/presentation_util/public/services/controls.ts rename to src/plugins/controls/public/services/controls.ts index 76af24960bfe3..83a3c8eec98d3 100644 --- a/src/plugins/presentation_util/public/services/controls.ts +++ b/src/plugins/controls/public/services/controls.ts @@ -7,18 +7,13 @@ */ import { EmbeddableFactory } from '../../../embeddable/public'; -import { - ControlEmbeddable, - ControlFactory, - ControlOutput, - ControlInput, -} from '../components/controls/types'; +import { ControlEmbeddable, ControlFactory, ControlOutput, ControlInput } from '../types'; export interface ControlTypeRegistry { [key: string]: ControlFactory; } -export interface PresentationControlsService { +export interface ControlsService { registerControlType: (factory: ControlFactory) => void; getControlFactory: < diff --git a/src/plugins/presentation_util/public/services/data.ts b/src/plugins/controls/public/services/data.ts similarity index 91% rename from src/plugins/presentation_util/public/services/data.ts rename to src/plugins/controls/public/services/data.ts index 44f29dcd2d3ad..0a99317e29a26 100644 --- a/src/plugins/presentation_util/public/services/data.ts +++ b/src/plugins/controls/public/services/data.ts @@ -8,6 +8,6 @@ import { DataPublicPluginStart } from '../../../data/public'; -export interface PresentationDataService { +export interface ControlsDataService { autocomplete: DataPublicPluginStart['autocomplete']; } diff --git a/src/plugins/controls/public/services/data_views.ts b/src/plugins/controls/public/services/data_views.ts new file mode 100644 index 0000000000000..2308366e27660 --- /dev/null +++ b/src/plugins/controls/public/services/data_views.ts @@ -0,0 +1,15 @@ +/* + * 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 { DataViewsPublicPluginStart } from '../../../data_views/public'; + +export interface ControlsDataViewsService { + get: DataViewsPublicPluginStart['get']; + getDefaultId: DataViewsPublicPluginStart['getDefaultId']; + getIdsWithTitle: DataViewsPublicPluginStart['getIdsWithTitle']; +} diff --git a/src/plugins/controls/public/services/index.ts b/src/plugins/controls/public/services/index.ts new file mode 100644 index 0000000000000..5730dbf68cefb --- /dev/null +++ b/src/plugins/controls/public/services/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { PluginServices } from '../../../presentation_util/public'; +import { ControlsDataViewsService } from './data_views'; +import { ControlsOverlaysService } from './overlays'; +import { registry as stubRegistry } from './stub'; +import { ControlsPluginStart } from '../types'; +import { ControlsDataService } from './data'; +import { ControlsService } from './controls'; + +export interface ControlsServices { + dataViews: ControlsDataViewsService; + overlays: ControlsOverlaysService; + data: ControlsDataService; + controls: ControlsService; +} + +export const pluginServices = new PluginServices(); + +export const getStubPluginServices = (): ControlsPluginStart => { + pluginServices.setRegistry(stubRegistry.start({})); + return { + ContextProvider: pluginServices.getContextProvider(), + controlsService: pluginServices.getServices().controls, + }; +}; diff --git a/src/plugins/presentation_util/public/services/stub/controls.ts b/src/plugins/controls/public/services/kibana/controls.ts similarity index 65% rename from src/plugins/presentation_util/public/services/stub/controls.ts rename to src/plugins/controls/public/services/kibana/controls.ts index e5dc84a3dd645..7c33ee8c33527 100644 --- a/src/plugins/presentation_util/public/services/stub/controls.ts +++ b/src/plugins/controls/public/services/kibana/controls.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { PluginServiceFactory } from '../create'; -import { getCommonControlsService, PresentationControlsService } from '../controls'; +import { PluginServiceFactory } from '../../../../presentation_util/public'; +import { getCommonControlsService, ControlsService } from '../controls'; -export type ControlsServiceFactory = PluginServiceFactory; +export type ControlsServiceFactory = PluginServiceFactory; export const controlsServiceFactory = () => getCommonControlsService(); diff --git a/src/plugins/presentation_util/public/services/kibana/data.ts b/src/plugins/controls/public/services/kibana/data.ts similarity index 71% rename from src/plugins/presentation_util/public/services/kibana/data.ts rename to src/plugins/controls/public/services/kibana/data.ts index 408e59fd4906c..4b4b9ad8afd81 100644 --- a/src/plugins/presentation_util/public/services/kibana/data.ts +++ b/src/plugins/controls/public/services/kibana/data.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import { PresentationUtilPluginStartDeps } from '../../types'; -import { KibanaPluginServiceFactory } from '../create'; -import { PresentationDataService } from '../data'; +import { ControlsDataService } from '../data'; +import { ControlsPluginStartDeps } from '../../types'; +import { KibanaPluginServiceFactory } from '../../../../presentation_util/public'; export type DataServiceFactory = KibanaPluginServiceFactory< - PresentationDataService, - PresentationUtilPluginStartDeps + ControlsDataService, + ControlsPluginStartDeps >; export const dataServiceFactory: DataServiceFactory = ({ startPlugins }) => { diff --git a/src/plugins/controls/public/services/kibana/data_views.ts b/src/plugins/controls/public/services/kibana/data_views.ts new file mode 100644 index 0000000000000..c878423390a64 --- /dev/null +++ b/src/plugins/controls/public/services/kibana/data_views.ts @@ -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 { ControlsPluginStartDeps } from '../../types'; +import { ControlsDataViewsService } from '../data_views'; +import { KibanaPluginServiceFactory } from '../../../../presentation_util/public'; + +export type DataViewsServiceFactory = KibanaPluginServiceFactory< + ControlsDataViewsService, + ControlsPluginStartDeps +>; + +export const dataViewsServiceFactory: DataViewsServiceFactory = ({ startPlugins }) => { + const { + dataViews: { get, getIdsWithTitle, getDefaultId }, + } = startPlugins; + + return { + get, + getDefaultId, + getIdsWithTitle, + }; +}; diff --git a/src/plugins/controls/public/services/kibana/index.ts b/src/plugins/controls/public/services/kibana/index.ts new file mode 100644 index 0000000000000..5f7f05705203e --- /dev/null +++ b/src/plugins/controls/public/services/kibana/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { + PluginServiceProviders, + KibanaPluginServiceParams, + PluginServiceProvider, + PluginServiceRegistry, +} from '../../../../presentation_util/public'; +import { ControlsPluginStartDeps } from '../../types'; +import { ControlsServices } from '..'; + +import { dataViewsServiceFactory } from './data_views'; +import { controlsServiceFactory } from './controls'; +import { overlaysServiceFactory } from './overlays'; +import { dataServiceFactory } from './data'; + +export const providers: PluginServiceProviders< + ControlsServices, + KibanaPluginServiceParams +> = { + data: new PluginServiceProvider(dataServiceFactory), + overlays: new PluginServiceProvider(overlaysServiceFactory), + controls: new PluginServiceProvider(controlsServiceFactory), + dataViews: new PluginServiceProvider(dataViewsServiceFactory), +}; + +export const registry = new PluginServiceRegistry< + ControlsServices, + KibanaPluginServiceParams +>(providers); diff --git a/src/plugins/presentation_util/public/services/kibana/overlays.ts b/src/plugins/controls/public/services/kibana/overlays.ts similarity index 71% rename from src/plugins/presentation_util/public/services/kibana/overlays.ts rename to src/plugins/controls/public/services/kibana/overlays.ts index b3a8d3a6e040a..43b8bd61ccb4a 100644 --- a/src/plugins/presentation_util/public/services/kibana/overlays.ts +++ b/src/plugins/controls/public/services/kibana/overlays.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import { PresentationUtilPluginStartDeps } from '../../types'; -import { KibanaPluginServiceFactory } from '../create'; -import { PresentationOverlaysService } from '../overlays'; +import { ControlsPluginStartDeps } from '../../types'; +import { ControlsOverlaysService } from '../overlays'; +import { KibanaPluginServiceFactory } from '../../../../presentation_util/public'; export type OverlaysServiceFactory = KibanaPluginServiceFactory< - PresentationOverlaysService, - PresentationUtilPluginStartDeps + ControlsOverlaysService, + ControlsPluginStartDeps >; export const overlaysServiceFactory: OverlaysServiceFactory = ({ coreStart }) => { const { diff --git a/src/plugins/presentation_util/public/services/overlays.ts b/src/plugins/controls/public/services/overlays.ts similarity index 93% rename from src/plugins/presentation_util/public/services/overlays.ts rename to src/plugins/controls/public/services/overlays.ts index ee90de5231896..9a30fca209c2f 100644 --- a/src/plugins/presentation_util/public/services/overlays.ts +++ b/src/plugins/controls/public/services/overlays.ts @@ -13,7 +13,7 @@ import { OverlayRef, } from '../../../../core/public'; -export interface PresentationOverlaysService { +export interface ControlsOverlaysService { openFlyout(mount: MountPoint, options?: OverlayFlyoutOpenOptions): OverlayRef; openConfirm(message: MountPoint | string, options?: OverlayModalConfirmOptions): Promise; } diff --git a/src/plugins/presentation_util/public/services/kibana/controls.ts b/src/plugins/controls/public/services/storybook/controls.ts similarity index 65% rename from src/plugins/presentation_util/public/services/kibana/controls.ts rename to src/plugins/controls/public/services/storybook/controls.ts index e5dc84a3dd645..7c33ee8c33527 100644 --- a/src/plugins/presentation_util/public/services/kibana/controls.ts +++ b/src/plugins/controls/public/services/storybook/controls.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { PluginServiceFactory } from '../create'; -import { getCommonControlsService, PresentationControlsService } from '../controls'; +import { PluginServiceFactory } from '../../../../presentation_util/public'; +import { getCommonControlsService, ControlsService } from '../controls'; -export type ControlsServiceFactory = PluginServiceFactory; +export type ControlsServiceFactory = PluginServiceFactory; export const controlsServiceFactory = () => getCommonControlsService(); diff --git a/src/plugins/presentation_util/public/services/storybook/data.ts b/src/plugins/controls/public/services/storybook/data.ts similarity index 83% rename from src/plugins/presentation_util/public/services/storybook/data.ts rename to src/plugins/controls/public/services/storybook/data.ts index 841ee1bd9be71..6d4942b358ac3 100644 --- a/src/plugins/presentation_util/public/services/storybook/data.ts +++ b/src/plugins/controls/public/services/storybook/data.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ +import { PluginServiceFactory } from '../../../../presentation_util/public'; import { DataPublicPluginStart } from '../../../../data/public'; import { DataViewField } from '../../../../data_views/common'; -import { PresentationDataService } from '../data'; -import { PluginServiceFactory } from '../create'; +import { ControlsDataService } from '../data'; let valueSuggestionMethod = ({ field, query }: { field: DataViewField; query: string }) => Promise.resolve(['storybook', 'default', 'values']); @@ -17,7 +17,7 @@ export const replaceValueSuggestionMethod = ( newMethod: ({ field, query }: { field: DataViewField; query: string }) => Promise ) => (valueSuggestionMethod = newMethod); -export type DataServiceFactory = PluginServiceFactory; +export type DataServiceFactory = PluginServiceFactory; export const dataServiceFactory: DataServiceFactory = () => ({ autocomplete: { getValueSuggestions: valueSuggestionMethod, diff --git a/src/plugins/controls/public/services/storybook/data_views.ts b/src/plugins/controls/public/services/storybook/data_views.ts new file mode 100644 index 0000000000000..5248dfbd70507 --- /dev/null +++ b/src/plugins/controls/public/services/storybook/data_views.ts @@ -0,0 +1,29 @@ +/* + * 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 { PluginServiceFactory } from '../../../../presentation_util/public'; +import { DataViewsPublicPluginStart } from '../../../../data_views/public'; +import { ControlsDataViewsService } from '../data_views'; +import { DataView } from '../../../../data_views/common'; + +export type DataViewsServiceFactory = PluginServiceFactory; + +let currentDataView: DataView; +export const injectStorybookDataView = (dataView: DataView) => (currentDataView = dataView); + +export const dataViewsServiceFactory: DataViewsServiceFactory = () => ({ + get: (() => + new Promise((r) => + setTimeout(() => r(currentDataView), 100) + ) as unknown) as DataViewsPublicPluginStart['get'], + getIdsWithTitle: (() => + new Promise((r) => + setTimeout(() => r([{ id: currentDataView.id, title: currentDataView.title }]), 100) + ) as unknown) as DataViewsPublicPluginStart['getIdsWithTitle'], + getDefaultId: () => Promise.resolve(currentDataView?.id ?? null), +}); diff --git a/src/plugins/controls/public/services/storybook/index.ts b/src/plugins/controls/public/services/storybook/index.ts new file mode 100644 index 0000000000000..36d8e7e78869d --- /dev/null +++ b/src/plugins/controls/public/services/storybook/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { + PluginServices, + PluginServiceProviders, + PluginServiceProvider, + PluginServiceRegistry, +} from '../../../../presentation_util/public'; +import { ControlsServices } from '..'; +import { dataServiceFactory } from './data'; +import { overlaysServiceFactory } from './overlays'; +import { controlsServiceFactory } from './controls'; +import { dataViewsServiceFactory } from './data_views'; + +export type { ControlsServices } from '..'; + +export const providers: PluginServiceProviders = { + dataViews: new PluginServiceProvider(dataViewsServiceFactory), + data: new PluginServiceProvider(dataServiceFactory), + overlays: new PluginServiceProvider(overlaysServiceFactory), + controls: new PluginServiceProvider(controlsServiceFactory), +}; + +export const pluginServices = new PluginServices(); + +export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/presentation_util/public/services/storybook/overlays.tsx b/src/plugins/controls/public/services/storybook/overlays.tsx similarity index 95% rename from src/plugins/presentation_util/public/services/storybook/overlays.tsx rename to src/plugins/controls/public/services/storybook/overlays.tsx index 50194fb636fa4..9ab4ea0b2c450 100644 --- a/src/plugins/presentation_util/public/services/storybook/overlays.tsx +++ b/src/plugins/controls/public/services/storybook/overlays.tsx @@ -7,20 +7,20 @@ */ import { EuiConfirmModal, EuiFlyout } from '@elastic/eui'; -import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subject } from 'rxjs'; +import React from 'react'; import { MountPoint, OverlayFlyoutOpenOptions, OverlayModalConfirmOptions, OverlayRef, } from '../../../../../core/public'; +import { ControlsOverlaysService } from '../overlays'; import { MountWrapper } from '../../../../../core/public/utils'; -import { PluginServiceFactory } from '../create'; -import { PresentationOverlaysService } from '../overlays'; +import { PluginServiceFactory } from '../../../../presentation_util/public'; -type OverlaysServiceFactory = PluginServiceFactory; +type OverlaysServiceFactory = PluginServiceFactory; /** * This code is a storybook stub version of src/core/public/overlays/overlay_service.ts diff --git a/src/plugins/presentation_util/public/services/storybook/controls.ts b/src/plugins/controls/public/services/stub/controls.ts similarity index 65% rename from src/plugins/presentation_util/public/services/storybook/controls.ts rename to src/plugins/controls/public/services/stub/controls.ts index e5dc84a3dd645..7c33ee8c33527 100644 --- a/src/plugins/presentation_util/public/services/storybook/controls.ts +++ b/src/plugins/controls/public/services/stub/controls.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { PluginServiceFactory } from '../create'; -import { getCommonControlsService, PresentationControlsService } from '../controls'; +import { PluginServiceFactory } from '../../../../presentation_util/public'; +import { getCommonControlsService, ControlsService } from '../controls'; -export type ControlsServiceFactory = PluginServiceFactory; +export type ControlsServiceFactory = PluginServiceFactory; export const controlsServiceFactory = () => getCommonControlsService(); diff --git a/src/plugins/controls/public/services/stub/index.ts b/src/plugins/controls/public/services/stub/index.ts new file mode 100644 index 0000000000000..6927aa65c12b8 --- /dev/null +++ b/src/plugins/controls/public/services/stub/index.ts @@ -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 { + PluginServiceProviders, + PluginServiceProvider, + PluginServiceRegistry, +} from '../../../../presentation_util/public'; +import { ControlsServices } from '..'; +import { overlaysServiceFactory } from './overlays'; +import { controlsServiceFactory } from './controls'; + +import { dataServiceFactory } from '../storybook/data'; +import { dataViewsServiceFactory } from '../storybook/data_views'; + +export const providers: PluginServiceProviders = { + data: new PluginServiceProvider(dataServiceFactory), + overlays: new PluginServiceProvider(overlaysServiceFactory), + controls: new PluginServiceProvider(controlsServiceFactory), + dataViews: new PluginServiceProvider(dataViewsServiceFactory), +}; + +export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/presentation_util/public/services/stub/overlays.ts b/src/plugins/controls/public/services/stub/overlays.ts similarity index 81% rename from src/plugins/presentation_util/public/services/stub/overlays.ts rename to src/plugins/controls/public/services/stub/overlays.ts index ecdec96d600d8..3c363111a6646 100644 --- a/src/plugins/presentation_util/public/services/stub/overlays.ts +++ b/src/plugins/controls/public/services/stub/overlays.ts @@ -12,10 +12,10 @@ import { OverlayModalConfirmOptions, OverlayRef, } from '../../../../../core/public'; -import { PluginServiceFactory } from '../create'; -import { PresentationOverlaysService } from '../overlays'; +import { PluginServiceFactory } from '../../../../presentation_util/public'; +import { ControlsOverlaysService } from '../overlays'; -type OverlaysServiceFactory = PluginServiceFactory; +type OverlaysServiceFactory = PluginServiceFactory; class StubRef implements OverlayRef { public readonly onClose: Promise = Promise.resolve(); diff --git a/src/plugins/presentation_util/public/components/controls/types.ts b/src/plugins/controls/public/types.ts similarity index 60% rename from src/plugins/presentation_util/public/components/controls/types.ts rename to src/plugins/controls/public/types.ts index 9d530fefe7373..70438baec756c 100644 --- a/src/plugins/presentation_util/public/components/controls/types.ts +++ b/src/plugins/controls/public/types.ts @@ -7,9 +7,18 @@ */ import { Filter } from '@kbn/es-query'; -import { DataView } from '../../../../data_views/public'; -import { ControlInput } from '../../../common/controls/types'; -import { EmbeddableFactory, EmbeddableOutput, IEmbeddable } from '../../../../embeddable/public'; + +import { + EmbeddableFactory, + EmbeddableOutput, + EmbeddableSetup, + EmbeddableStart, + IEmbeddable, +} from '../../embeddable/public'; +import { ControlInput } from '../common/types'; +import { DataPublicPluginStart } from '../../data/public'; +import { ControlsService } from './services/controls'; +import { DataView, DataViewsPublicPluginStart } from '../../data_views/public'; export interface CommonControlOutput { filters?: Filter[]; @@ -43,6 +52,25 @@ export interface ControlEditorProps { } /** - * Re-export control types from common + * Plugin types */ -export * from '../../../common/controls/types'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ControlsPluginSetup {} + +export interface ControlsPluginStart { + controlsService: ControlsService; + ContextProvider: React.FC; +} + +export interface ControlsPluginSetupDeps { + embeddable: EmbeddableSetup; +} +export interface ControlsPluginStartDeps { + data: DataPublicPluginStart; + embeddable: EmbeddableStart; + dataViews: DataViewsPublicPluginStart; +} + +// re-export from common +export type { ControlWidth, ControlInput, ControlStyle } from '../common/types'; diff --git a/src/plugins/presentation_util/server/controls/control_group/control_group_container_factory.ts b/src/plugins/controls/server/control_group/control_group_container_factory.ts similarity index 71% rename from src/plugins/presentation_util/server/controls/control_group/control_group_container_factory.ts rename to src/plugins/controls/server/control_group/control_group_container_factory.ts index 17dcbbd249435..39e1a9fbb12c9 100644 --- a/src/plugins/presentation_util/server/controls/control_group/control_group_container_factory.ts +++ b/src/plugins/controls/server/control_group/control_group_container_factory.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import { EmbeddablePersistableStateService } from 'src/plugins/embeddable/common'; -import { EmbeddableRegistryDefinition } from '../../../../embeddable/server'; -import { CONTROL_GROUP_TYPE } from '../../../common/controls'; +import { EmbeddablePersistableStateService } from '../../../embeddable/common'; +import { EmbeddableRegistryDefinition } from '../../../embeddable/server'; +import { CONTROL_GROUP_TYPE } from '../../common'; import { createControlGroupExtract, createControlGroupInject, -} from '../../../common/controls/control_group/control_group_persistable_state'; +} from '../../common/control_group/control_group_persistable_state'; export const controlGroupContainerPersistableStateServiceFactory = ( persistableStateService: EmbeddablePersistableStateService diff --git a/src/plugins/presentation_util/server/controls/control_types/options_list/options_list_embeddable_factory.ts b/src/plugins/controls/server/control_types/options_list/options_list_embeddable_factory.ts similarity index 72% rename from src/plugins/presentation_util/server/controls/control_types/options_list/options_list_embeddable_factory.ts rename to src/plugins/controls/server/control_types/options_list/options_list_embeddable_factory.ts index b9d69ea489274..846e3cfe9342c 100644 --- a/src/plugins/presentation_util/server/controls/control_types/options_list/options_list_embeddable_factory.ts +++ b/src/plugins/controls/server/control_types/options_list/options_list_embeddable_factory.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import { EmbeddableRegistryDefinition } from '../../../../../embeddable/server'; -import { OPTIONS_LIST_CONTROL } from '../../../../common/controls'; +import { EmbeddableRegistryDefinition } from '../../../../embeddable/server'; +import { OPTIONS_LIST_CONTROL } from '../../../common'; import { createOptionsListExtract, createOptionsListInject, -} from '../../../../common/controls/control_types/options_list/options_list_persistable_state'; +} from '../../../common/control_types/options_list/options_list_persistable_state'; export const optionsListPersistableStateServiceFactory = (): EmbeddableRegistryDefinition => { return { diff --git a/src/plugins/presentation_util/public/components/controls/index.ts b/src/plugins/controls/server/index.ts similarity index 79% rename from src/plugins/presentation_util/public/components/controls/index.ts rename to src/plugins/controls/server/index.ts index c110bc348498d..5928186715210 100644 --- a/src/plugins/presentation_util/public/components/controls/index.ts +++ b/src/plugins/controls/server/index.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -export * from './control_group'; -export * from './control_types'; -export * from './types'; +import { ControlsPlugin } from './plugin'; + +export const plugin = () => new ControlsPlugin(); diff --git a/src/plugins/controls/server/plugin.ts b/src/plugins/controls/server/plugin.ts new file mode 100644 index 0000000000000..fa7b7970c7e64 --- /dev/null +++ b/src/plugins/controls/server/plugin.ts @@ -0,0 +1,33 @@ +/* + * 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 { CoreSetup, Plugin } from 'kibana/server'; +import { EmbeddableSetup } from '../../embeddable/server'; +import { controlGroupContainerPersistableStateServiceFactory } from './control_group/control_group_container_factory'; +import { optionsListPersistableStateServiceFactory } from './control_types/options_list/options_list_embeddable_factory'; + +interface SetupDeps { + embeddable: EmbeddableSetup; +} + +export class ControlsPlugin implements Plugin { + public setup(core: CoreSetup, plugins: SetupDeps) { + plugins.embeddable.registerEmbeddableFactory(optionsListPersistableStateServiceFactory()); + + plugins.embeddable.registerEmbeddableFactory( + controlGroupContainerPersistableStateServiceFactory(plugins.embeddable) + ); + return {}; + } + + public start() { + return {}; + } + + public stop() {} +} diff --git a/src/plugins/controls/storybook/decorator.tsx b/src/plugins/controls/storybook/decorator.tsx new file mode 100644 index 0000000000000..603bddf320627 --- /dev/null +++ b/src/plugins/controls/storybook/decorator.tsx @@ -0,0 +1,46 @@ +/* + * 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 from 'react'; + +import { DecoratorFn } from '@storybook/react'; +import { I18nProvider } from '@kbn/i18n-react'; +import { pluginServices } from '../public/services'; +import { ControlsServices } from '../public/services'; +import { providers } from '../public/services/storybook'; +import { PluginServiceRegistry } from '../../presentation_util/public'; +import { KibanaContextProvider as KibanaReactProvider } from '../../kibana_react/public'; + +const settings = new Map(); +settings.set('darkMode', true); + +const services = { + http: { + basePath: { + get: () => '', + prepend: () => '', + remove: () => '', + serverBasePath: '', + }, + }, + uiSettings: settings, +}; + +export const servicesContextDecorator: DecoratorFn = (story: Function, storybook) => { + const registry = new PluginServiceRegistry(providers); + pluginServices.setRegistry(registry.start(storybook.args)); + const ContextProvider = pluginServices.getContextProvider(); + + return ( + + + {story()} + + + ); +}; diff --git a/src/plugins/controls/storybook/main.ts b/src/plugins/controls/storybook/main.ts new file mode 100644 index 0000000000000..13f55f8be2df8 --- /dev/null +++ b/src/plugins/controls/storybook/main.ts @@ -0,0 +1,16 @@ +/* + * 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 { defaultConfigWebFinal } from '@kbn/storybook'; + +// We have to do this because the kbn/storybook preset overrides the manager entries, +// so we can't customize the theme. +module.exports = { + ...defaultConfigWebFinal, + addons: ['@storybook/addon-a11y', '@storybook/addon-essentials'], +}; diff --git a/src/plugins/controls/storybook/manager.ts b/src/plugins/controls/storybook/manager.ts new file mode 100644 index 0000000000000..1b8c9aa89e687 --- /dev/null +++ b/src/plugins/controls/storybook/manager.ts @@ -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 { addons } from '@storybook/addons'; +import { create } from '@storybook/theming'; +import { PANEL_ID } from '@storybook/addon-actions'; + +// @ts-expect-error There's probably a better way to do this. +import { registerThemeSwitcherAddon } from '@kbn/storybook/target_node/lib/register_theme_switcher_addon'; + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: 'Kibana Controls Storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/main/src/plugins/controls', + }), + showPanel: true.valueOf, + selectedPanel: PANEL_ID, +}); + +registerThemeSwitcherAddon(); diff --git a/src/plugins/controls/storybook/preview.tsx b/src/plugins/controls/storybook/preview.tsx new file mode 100644 index 0000000000000..e71f4e08b2027 --- /dev/null +++ b/src/plugins/controls/storybook/preview.tsx @@ -0,0 +1,29 @@ +/* + * 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 from 'react'; +import { addDecorator } from '@storybook/react'; +import { Title, Subtitle, Description, Primary, Stories } from '@storybook/addon-docs/blocks'; + +import { servicesContextDecorator } from './decorator'; + +addDecorator(servicesContextDecorator); + +export const parameters = { + docs: { + page: () => ( + <> + + <Subtitle /> + <Description /> + <Primary /> + <Stories /> + </> + ), + }, +}; diff --git a/src/plugins/controls/tsconfig.json b/src/plugins/controls/tsconfig.json new file mode 100644 index 0000000000000..ed0c2e63011d0 --- /dev/null +++ b/src/plugins/controls/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "extraPublicDirs": ["common"], + "include": [ + "common/**/*", + "public/**/*", + "public/**/*.json", + "server/**/*", + "storybook/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../saved_objects/tsconfig.json" }, + { "path": "../kibana_react/tsconfig.json" }, + { "path": "../embeddable/tsconfig.json" }, + { "path": "../presentation_util/tsconfig.json" }, + { "path": "../kibana_react/tsconfig.json" }, + { "path": "../data/tsconfig.json" } + ] +} diff --git a/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts b/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts index bc8f56fc8c4dc..c0768331d20c5 100644 --- a/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts +++ b/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts @@ -17,7 +17,7 @@ import { DashboardContainerStateWithType, DashboardPanelState, } from '../types'; -import { CONTROL_GROUP_TYPE } from '../../../presentation_util/common/lib'; +import { CONTROL_GROUP_TYPE } from '../../../controls/common'; const getPanelStatePrefix = (state: DashboardPanelState) => `${state.explicitInput.id}:`; diff --git a/src/plugins/dashboard/common/saved_dashboard_references.ts b/src/plugins/dashboard/common/saved_dashboard_references.ts index bc7358b49ceb4..346190e4fef91 100644 --- a/src/plugins/dashboard/common/saved_dashboard_references.ts +++ b/src/plugins/dashboard/common/saved_dashboard_references.ts @@ -19,7 +19,7 @@ import { convertSavedDashboardPanelToPanelState, } from './embeddable/embeddable_saved_object_converters'; import { SavedDashboardPanel } from './types'; -import { CONTROL_GROUP_TYPE } from '../../presentation_util/common/lib'; +import { CONTROL_GROUP_TYPE } from '../../controls/common'; export interface ExtractDeps { embeddablePersistableStateService: EmbeddablePersistableStateService; diff --git a/src/plugins/dashboard/common/types.ts b/src/plugins/dashboard/common/types.ts index bfe53514969d7..29e3d48d7f0d5 100644 --- a/src/plugins/dashboard/common/types.ts +++ b/src/plugins/dashboard/common/types.ts @@ -22,7 +22,7 @@ import { } from './bwc/types'; import { GridData } from './embeddable/types'; -import { ControlGroupInput } from '../../presentation_util/common/controls/control_group/types'; +import { ControlGroupInput } from '../../controls/common'; export type PanelId = string; export type SavedObjectId = string; diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json index 2be6e9b269e71..683a1a551f81d 100644 --- a/src/plugins/dashboard/kibana.json +++ b/src/plugins/dashboard/kibana.json @@ -9,6 +9,7 @@ "requiredPlugins": [ "data", "embeddable", + "controls", "inspector", "navigation", "savedObjects", diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 36261fbe130a3..d9733d1a35586 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -47,7 +47,7 @@ import { combineDashboardFiltersWithControlGroupFilters, syncDashboardControlGroup, } from '../lib/dashboard_control_group'; -import { ControlGroupContainer } from '../../../../presentation_util/public'; +import { ControlGroupContainer } from '../../../../controls/public'; export interface DashboardContainerServices { ExitFullScreenButton: React.ComponentType<any>; diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx index f7cf329d0ae35..7be36a954d2f1 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx @@ -28,7 +28,7 @@ import { ControlGroupInput, ControlGroupOutput, CONTROL_GROUP_TYPE, -} from '../../../../presentation_util/public'; +} from '../../../../controls/public'; import { getDefaultDashboardControlGroupInput } from '../../dashboard_constants'; export type DashboardContainerFactory = EmbeddableFactory< diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index 1e19e495585fe..a862c084de400 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -13,7 +13,7 @@ import { DashboardContainer, DashboardReactContextValue } from '../dashboard_con import { DashboardGrid } from '../grid'; import { context } from '../../../services/kibana_react'; import { DashboardEmptyScreen } from '../empty_screen/dashboard_empty_screen'; -import { ControlGroupContainer } from '../../../../../presentation_util/public'; +import { ControlGroupContainer } from '../../../../../controls/public'; export interface DashboardViewportProps { container: DashboardContainer; diff --git a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts index 8d55af5808da6..89ad65f58278f 100644 --- a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts @@ -21,7 +21,7 @@ import { } from '../../types'; import { convertSavedPanelsToPanelMap } from './convert_dashboard_panels'; import { deserializeControlGroupFromDashboardSavedObject } from './dashboard_control_group'; -import { ControlGroupInput } from '../../../../presentation_util/public'; +import { ControlGroupInput } from '../../../../controls/public'; interface SavedObjectToDashboardStateProps { version: string; diff --git a/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts b/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts index aaf6c5f0af4fc..90d5a67c3da47 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_control_group.ts @@ -15,7 +15,7 @@ import { DashboardContainer } from '..'; import { DashboardState } from '../../types'; import { getDefaultDashboardControlGroupInput } from '../../dashboard_constants'; import { DashboardContainerInput, DashboardSavedObject } from '../..'; -import { ControlGroupContainer, ControlGroupInput } from '../../../../presentation_util/public'; +import { ControlGroupContainer, ControlGroupInput } from '../../../../controls/public'; // only part of the control group input should be stored in dashboard state. The rest is passed down from the dashboard. export interface DashboardControlGroupInput { diff --git a/src/plugins/dashboard/public/dashboard_constants.ts b/src/plugins/dashboard/public/dashboard_constants.ts index 6f9a30e3a7041..9063b279c25f2 100644 --- a/src/plugins/dashboard/public/dashboard_constants.ts +++ b/src/plugins/dashboard/public/dashboard_constants.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ControlStyle } from '../../presentation_util/public'; +import type { ControlStyle } from '../../controls/public'; export const DASHBOARD_STATE_STORAGE_KEY = '_a'; diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts index d8e8b70fc1340..52ecb9549d54d 100644 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts +++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts @@ -18,7 +18,7 @@ import { extractReferences, injectReferences } from '../../common/saved_dashboar import { SavedObjectAttributes, SavedObjectReference } from '../../../../core/types'; import { DashboardOptions } from '../types'; -import { ControlStyle } from '../../../presentation_util/public'; +import { ControlStyle } from '../../../controls/public'; export interface DashboardSavedObject extends SavedObject { id?: string; diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index 78a1958a43156..680d06780543a 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -6,23 +6,18 @@ "declaration": true, "declarationMap": true }, - "include": [ - "*.ts", - ".storybook/**/*", - "common/**/*", - "public/**/*", - "server/**/*", - ], + "include": ["*.ts", ".storybook/**/*", "common/**/*", "public/**/*", "server/**/*"], "references": [ { "path": "../../core/tsconfig.json" }, { "path": "../inspector/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, { "path": "../kibana_utils/tsconfig.json" }, { "path": "../share/tsconfig.json" }, + { "path": "../controls/tsconfig.json" }, { "path": "../presentation_util/tsconfig.json" }, { "path": "../url_forwarding/tsconfig.json" }, { "path": "../usage_collection/tsconfig.json" }, - { "path": "../data/tsconfig.json"}, + { "path": "../data/tsconfig.json" }, { "path": "../embeddable/tsconfig.json" }, { "path": "../home/tsconfig.json" }, { "path": "../navigation/tsconfig.json" }, @@ -32,6 +27,6 @@ { "path": "../charts/tsconfig.json" }, { "path": "../discover/tsconfig.json" }, { "path": "../visualizations/tsconfig.json" }, - { "path": "../../../x-pack/plugins/spaces/tsconfig.json" }, + { "path": "../../../x-pack/plugins/spaces/tsconfig.json" } ] } diff --git a/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx b/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx index a38649f13fb32..161fcef8be0fc 100644 --- a/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx +++ b/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx @@ -13,7 +13,7 @@ import { Observable } from 'rxjs'; import { CoreTheme } from 'kibana/public'; import { CoreSetup } from '../../../../core/public'; import { KibanaThemeProvider } from '../../../kibana_react/public'; -import { getElasticLogo, isValidUrl, defaultTheme$ } from '../../../presentation_util/public'; +import { getElasticLogo, defaultTheme$, isValidUrl } from '../../../presentation_util/public'; import { ImageRendererConfig } from '../../common/types'; const strings = { diff --git a/src/plugins/presentation_util/README.mdx b/src/plugins/presentation_util/README.mdx index 575e8002e6eb8..2cbb03232b9dd 100755 --- a/src/plugins/presentation_util/README.mdx +++ b/src/plugins/presentation_util/README.mdx @@ -209,3 +209,98 @@ export function MyComponent() { } ``` </DocAccordion> + +## Redux Embeddables +The Redux Embeddables system allows embeddable authors to interact with their embeddables in a standardized way using Redux toolkit. This wrapper abstracts away store and slice creation, embeddable input sync, and context creation. To use this system, a developer can wrap their components in the ReduxEmbeddableWrapper, supplying only an object of reducers. + +### Reducers +The reducer object expected by the ReduxEmbeddableWrapper is the same type as the reducers expected by [Redux Toolkit's CreateSlice](https://redux-toolkit.js.org/api/createslice). + +<DocAccordion buttonContent="Reducers Example" initialIsOpen> +```ts +// my_embeddable_reducers.ts +import { MyEmbeddableInput } from './my_embeddable'; + +export const myEmbeddableReducers = { + setSpecialBoolean: ( + state: WritableDraft<MyEmbeddableInput>, + action: PayloadAction<MyEmbeddableInput['specialBoolean']> + ) => { + state.specialBoolean = action.payload; + } +} + +``` +</DocAccordion> + +### Lazy Component and Types +Because the ReduxEmbeddableWrapper is a lazy component, it also must be unwrapped with the `withSuspense` component from Presentation Util. When you await this component, you must also pass in the type information so that the redux store and actions are properly typed. + + <DocAccordion buttonContent="Awaiting LazyReduxEmbeddableWrapper" initialIsOpen> + ```ts + // my_embeddable.tsx + + import { + withSuspense, + LazyReduxEmbeddableWrapper, + ReduxEmbeddableWrapperPropsWithChildren, + } from '../../../../presentation_util/public'; + + export interface MyEmbeddableInput { + specialBoolean: boolean + } + + const MyEmbeddableReduxWrapper = withSuspense< + ReduxEmbeddableWrapperPropsWithChildren<MyEmbeddableInput> + >(LazyReduxEmbeddableWrapper); + + ``` +</DocAccordion> + +The ReduxEmbeddableWrapper should be used inside of embeddable classes, and should wrap all components under the embeddable in the render function. + +<DocAccordion buttonContent="Wrapping Embeddable Render" initialIsOpen> + ```ts + // my_embeddable.tsx + + public render(dom: HTMLElement) { + if (this.domNode) ReactDOM.unmountComponentAtNode(this.domNode); + this.domNode = dom; + ReactDOM.render( + <MyEmbeddableReduxWrapper embeddable={this} reducers={myEmbeddableReducers}> + <MyEmbeddableComponent /> + </MyEmbeddableReduxWrapper>, + dom + ); + } + ``` +</DocAccordion> + +### Accessing Actions and State + +From components under the embeddable, actions, containerActions, and the current state of the redux store are accessed via the ReduxEmbeddableContext. This context requires the input type and the type of the reducers, and will return the appropriately typed actions, a hook for dispatching actions, a selector to get the current redux state, and a suite of `containerActions` if the embeddable is a Container. + +<DocAccordion buttonContent="Accessing Redux Embeddable Context" initialIsOpen> + ```ts + // my_embeddable_component.tsx + import { useReduxEmbeddableContext } from '../../../../presentation_util/public'; + + const { + useEmbeddableSelector, + useEmbeddableDispatch, + actions: { setSpecialBoolean }, + } = useReduxEmbeddableContext< + MyEmbeddableInput, + typeof myEmbeddableReducers + >(); + + const dispatch = useEmbeddableDispatch(); + + // current state + const { specialBoolean } = useEmbeddableSelector((state) => state); + + // change specialBoolean after 5 seconds + setTimeout(() => dispatch(setSpecialBoolean(false)), 5000); + ``` +</DocAccordion> + diff --git a/src/plugins/presentation_util/common/lib/index.ts b/src/plugins/presentation_util/common/lib/index.ts index 030780c130fa5..3fe90009ad8df 100644 --- a/src/plugins/presentation_util/common/lib/index.ts +++ b/src/plugins/presentation_util/common/lib/index.ts @@ -8,4 +8,3 @@ export * from './utils'; export * from './test_helpers'; -export * from '../controls'; diff --git a/src/plugins/presentation_util/kibana.json b/src/plugins/presentation_util/kibana.json index 32460a8455152..6c8d38a5f8a1e 100644 --- a/src/plugins/presentation_util/kibana.json +++ b/src/plugins/presentation_util/kibana.json @@ -9,16 +9,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "extraPublicDirs": [ - "common/lib" - ], - "requiredPlugins": [ - "savedObjects", - "data", - "dataViews", - "embeddable", - "kibanaReact", - "expressions" - ], + "extraPublicDirs": ["common/lib"], + "requiredPlugins": ["savedObjects", "kibanaReact", "embeddable", "expressions", "dataViews"], "optionalPlugins": [] } diff --git a/src/plugins/presentation_util/public/components/controls/__stories__/fixtures/flights.ts b/src/plugins/presentation_util/public/__stories__/fixtures/flights.ts similarity index 93% rename from src/plugins/presentation_util/public/components/controls/__stories__/fixtures/flights.ts rename to src/plugins/presentation_util/public/__stories__/fixtures/flights.ts index 921b7f3999faa..0ec82b1e1994b 100644 --- a/src/plugins/presentation_util/public/components/controls/__stories__/fixtures/flights.ts +++ b/src/plugins/presentation_util/public/__stories__/fixtures/flights.ts @@ -7,12 +7,8 @@ */ import { map, uniq } from 'lodash'; -import { flights } from '../fixtures/flights_data'; -import { - DataView, - DataViewField, - IIndexPatternFieldList, -} from '../../../../../../data_views/common'; +import { flights } from './flights_data'; +import { DataView, DataViewField, IIndexPatternFieldList } from '../../../../data_views/public'; export type Flight = typeof flights[number]; export type FlightField = keyof Flight; diff --git a/src/plugins/presentation_util/public/components/controls/__stories__/fixtures/flights_data.ts b/src/plugins/presentation_util/public/__stories__/fixtures/flights_data.ts similarity index 100% rename from src/plugins/presentation_util/public/components/controls/__stories__/fixtures/flights_data.ts rename to src/plugins/presentation_util/public/__stories__/fixtures/flights_data.ts diff --git a/src/plugins/presentation_util/public/__stories__/index.tsx b/src/plugins/presentation_util/public/__stories__/index.tsx index a5633c4a2dd1f..94904abf43b98 100644 --- a/src/plugins/presentation_util/public/__stories__/index.tsx +++ b/src/plugins/presentation_util/public/__stories__/index.tsx @@ -8,3 +8,5 @@ export * from './render'; export * from './wait_for'; +export * from './fixtures/flights'; +export * from './fixtures/flights_data'; diff --git a/src/plugins/presentation_util/public/components/controls/control_group/control_group_strings.ts b/src/plugins/presentation_util/public/components/controls/control_group/control_group_strings.ts deleted file mode 100644 index 111b247d7417e..0000000000000 --- a/src/plugins/presentation_util/public/components/controls/control_group/control_group_strings.ts +++ /dev/null @@ -1,206 +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 { i18n } from '@kbn/i18n'; - -export const ControlGroupStrings = { - getEmbeddableTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.title', { - defaultMessage: 'Control group', - }), - emptyState: { - getCallToAction: () => - i18n.translate('presentationUtil.inputControls.controlGroup.emptyState.callToAction', { - defaultMessage: 'Controls let you filter and interact with your dashboard data', - }), - getAddControlButtonTitle: () => - i18n.translate( - 'presentationUtil.inputControls.controlGroup.emptyState.addControlButtonTitle', - { - defaultMessage: 'Add control', - } - ), - getTwoLineLoadingTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.emptyState.twoLineLoadingTitle', { - defaultMessage: '...', - }), - }, - manageControl: { - getFlyoutCreateTitle: () => - i18n.translate( - 'presentationUtil.inputControls.controlGroup.manageControl.createFlyoutTitle', - { - defaultMessage: 'Create control', - } - ), - getFlyoutEditTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.manageControl.editFlyoutTitle', { - defaultMessage: 'Edit control', - }), - getTitleInputTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.manageControl.titleInputTitle', { - defaultMessage: 'Title', - }), - getWidthInputTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.manageControl.widthInputTitle', { - defaultMessage: 'Control size', - }), - getSaveChangesTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.manageControl.saveChangesTitle', { - defaultMessage: 'Save and close', - }), - getCancelTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.manageControl.cancelTitle', { - defaultMessage: 'Cancel', - }), - }, - management: { - getAddControlTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.addControl', { - defaultMessage: 'Add control', - }), - getManageButtonTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.buttonTitle', { - defaultMessage: 'Configure controls', - }), - getFlyoutTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.flyoutTitle', { - defaultMessage: 'Configure controls', - }), - getDefaultWidthTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.defaultWidthTitle', { - defaultMessage: 'Default size', - }), - getLayoutTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.layoutTitle', { - defaultMessage: 'Layout', - }), - getDeleteButtonTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.delete', { - defaultMessage: 'Delete control', - }), - getSetAllWidthsToDefaultTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.setAllWidths', { - defaultMessage: 'Set all sizes to default', - }), - getDeleteAllButtonTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.deleteAll', { - defaultMessage: 'Delete all', - }), - controlWidth: { - getWidthSwitchLegend: () => - i18n.translate( - 'presentationUtil.controls.controlGroup.management.layout.controlWidthLegend', - { - defaultMessage: 'Change control size', - } - ), - getAutoWidthTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.layout.auto', { - defaultMessage: 'Auto', - }), - getSmallWidthTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.layout.small', { - defaultMessage: 'Small', - }), - getMediumWidthTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.layout.medium', { - defaultMessage: 'Medium', - }), - getLargeWidthTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.layout.large', { - defaultMessage: 'Large', - }), - }, - controlStyle: { - getDesignSwitchLegend: () => - i18n.translate( - 'presentationUtil.controls.controlGroup.management.layout.designSwitchLegend', - { - defaultMessage: 'Switch control designs', - } - ), - getSingleLineTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.layout.singleLine', { - defaultMessage: 'Single line', - }), - getTwoLineTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.layout.twoLine', { - defaultMessage: 'Double line', - }), - }, - deleteControls: { - getDeleteAllTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.delete.deleteAllTitle', { - defaultMessage: 'Delete all controls?', - }), - getDeleteTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.delete.deleteTitle', { - defaultMessage: 'Delete control?', - }), - getSubtitle: () => - i18n.translate('presentationUtil.controls.controlGroup.management.delete.sub', { - defaultMessage: 'Controls are not recoverable once removed.', - }), - getConfirm: () => - i18n.translate('presentationUtil.controls.controlGroup.management.delete.confirm', { - defaultMessage: 'Delete', - }), - getCancel: () => - i18n.translate('presentationUtil.controls.controlGroup.management.delete.cancel', { - defaultMessage: 'Cancel', - }), - }, - discardChanges: { - getTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.discard.title', { - defaultMessage: 'Discard changes?', - }), - getSubtitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.discard.sub', { - defaultMessage: `Changes that you've made to this control will be discarded, are you sure you want to continue?`, - }), - getConfirm: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.discard.confirm', { - defaultMessage: 'Discard changes', - }), - getCancel: () => - i18n.translate('presentationUtil.controls.controlGroup.management.discard.cancel', { - defaultMessage: 'Cancel', - }), - }, - discardNewControl: { - getTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.deleteNew.title', { - defaultMessage: 'Discard new control', - }), - getSubtitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.deleteNew.sub', { - defaultMessage: `Changes that you've made to this control will be discarded, are you sure you want to continue?`, - }), - getConfirm: () => - i18n.translate('presentationUtil.inputControls.controlGroup.management.deleteNew.confirm', { - defaultMessage: 'Discard control', - }), - getCancel: () => - i18n.translate('presentationUtil.controls.controlGroup.management.deleteNew.cancel', { - defaultMessage: 'Cancel', - }), - }, - }, - floatingActions: { - getEditButtonTitle: () => - i18n.translate('presentationUtil.inputControls.controlGroup.floatingActions.editTitle', { - defaultMessage: 'Edit control', - }), - getRemoveButtonTitle: () => - i18n.translate('presentationUtil.controls.controlGroup.floatingActions.removeTitle', { - defaultMessage: 'Remove control', - }), - }, -}; diff --git a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.stories.tsx b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.stories.tsx index b8b0c46e7823d..f8c1539ecda28 100644 --- a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.stories.tsx +++ b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.stories.tsx @@ -12,7 +12,7 @@ import useMount from 'react-use/lib/useMount'; import { DataViewPicker } from './data_view_picker'; import { DataView, DataViewListItem } from '../../../../data_views/common'; import { injectStorybookDataView } from '../../services/storybook/data_views'; -import { storybookFlightsDataView } from '../controls/__stories__/fixtures/flights'; +import { storybookFlightsDataView } from '../../mocks'; import { pluginServices, registry, StorybookParams } from '../../services/storybook'; export default { diff --git a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx index 2911ae7a1e687..2391f945d478a 100644 --- a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx +++ b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx @@ -114,3 +114,7 @@ export function DataViewPicker({ </> ); } + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default DataViewPicker; diff --git a/src/plugins/presentation_util/public/components/field_picker/field_picker.stories.tsx b/src/plugins/presentation_util/public/components/field_picker/field_picker.stories.tsx index 023d2be949a73..f2462f3a25bb4 100644 --- a/src/plugins/presentation_util/public/components/field_picker/field_picker.stories.tsx +++ b/src/plugins/presentation_util/public/components/field_picker/field_picker.stories.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { FieldPicker } from './field_picker'; import { DataViewField } from '../../../../data_views/common'; -import { storybookFlightsDataView } from '../controls/__stories__/fixtures/flights'; +import { storybookFlightsDataView } from '../../mocks'; export default { component: FieldPicker, diff --git a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx index 54efe87a7f432..f9fb6f985b629 100644 --- a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx +++ b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx @@ -142,3 +142,7 @@ export const FieldPicker = ({ </EuiFlexGroup> ); }; + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default FieldPicker; diff --git a/src/plugins/presentation_util/public/components/index.tsx b/src/plugins/presentation_util/public/components/index.tsx index b64cf9e97be9d..5a254877399ed 100644 --- a/src/plugins/presentation_util/public/components/index.tsx +++ b/src/plugins/presentation_util/public/components/index.tsx @@ -8,6 +8,7 @@ import React, { Suspense, ComponentType, ReactElement, Ref } from 'react'; import { EuiLoadingSpinner, EuiErrorBoundary } from '@elastic/eui'; +import { ReduxEmbeddableWrapperType } from './redux_embeddables/redux_embeddable_wrapper'; /** * A HOC which supplies React.Suspense with a fallback component, and a `EuiErrorBoundary` to contain errors. @@ -38,6 +39,14 @@ export const LazySavedObjectSaveModalDashboard = React.lazy( () => import('./saved_object_save_modal_dashboard') ); +export const LazyReduxEmbeddableWrapper = React.lazy( + () => import('./redux_embeddables/redux_embeddable_wrapper') +) as ReduxEmbeddableWrapperType; // Lazy component needs to be casted due to generic type props + +export const LazyDataViewPicker = React.lazy(() => import('./data_view_picker/data_view_picker')); + +export const LazyFieldPicker = React.lazy(() => import('./field_picker/field_picker')); + /** * A lazily-loaded ExpressionInput component. */ diff --git a/src/plugins/presentation_util/common/controls/index.ts b/src/plugins/presentation_util/public/components/redux_embeddables/index.ts similarity index 59% rename from src/plugins/presentation_util/common/controls/index.ts rename to src/plugins/presentation_util/public/components/redux_embeddables/index.ts index b01a242bdfa5f..55fb913635e81 100644 --- a/src/plugins/presentation_util/common/controls/index.ts +++ b/src/plugins/presentation_util/public/components/redux_embeddables/index.ts @@ -6,5 +6,12 @@ * Side Public License, v 1. */ -export * from './control_group/types'; -export * from './control_types/options_list/types'; +export { + ReduxEmbeddableContext, + useReduxContainerContext, + useReduxEmbeddableContext, +} from './redux_embeddable_context'; +export type { + ReduxContainerContextServices, + ReduxEmbeddableWrapperPropsWithChildren, +} from './types'; diff --git a/src/plugins/presentation_util/public/components/redux_embeddables/redux_embeddable_context.ts b/src/plugins/presentation_util/public/components/redux_embeddables/redux_embeddable_context.ts index 159230e4de024..40fdab429ae55 100644 --- a/src/plugins/presentation_util/public/components/redux_embeddables/redux_embeddable_context.ts +++ b/src/plugins/presentation_util/public/components/redux_embeddables/redux_embeddable_context.ts @@ -7,12 +7,12 @@ */ import { createContext, useContext } from 'react'; -import { +import type { GenericEmbeddableReducers, ReduxContainerContextServices, ReduxEmbeddableContextServices, } from './types'; -import { ContainerInput, EmbeddableInput } from '../../../../embeddable/public'; +import type { ContainerInput, EmbeddableInput } from '../../../../embeddable/public'; /** * When creating the context, a generic EmbeddableInput as placeholder is used. This will later be cast to diff --git a/src/plugins/presentation_util/public/components/redux_embeddables/redux_embeddable_wrapper.tsx b/src/plugins/presentation_util/public/components/redux_embeddables/redux_embeddable_wrapper.tsx index 9e7b53fb21c3b..a23dcf944812b 100644 --- a/src/plugins/presentation_util/public/components/redux_embeddables/redux_embeddable_wrapper.tsx +++ b/src/plugins/presentation_util/public/components/redux_embeddables/redux_embeddable_wrapper.tsx @@ -15,9 +15,10 @@ import { Filter } from '@kbn/es-query'; import { isEqual } from 'lodash'; import { + ReduxEmbeddableWrapperProps, ReduxContainerContextServices, ReduxEmbeddableContextServices, - ReduxEmbeddableWrapperProps, + ReduxEmbeddableWrapperPropsWithChildren, } from './types'; import { IContainer, @@ -78,7 +79,7 @@ export const getExplicitInput = <InputType extends EmbeddableInput = EmbeddableI * or ReduxContainerContext to interface with the state of the embeddable. */ export const ReduxEmbeddableWrapper = <InputType extends EmbeddableInput = EmbeddableInput>( - props: PropsWithChildren<ReduxEmbeddableWrapperProps<InputType>> + props: ReduxEmbeddableWrapperPropsWithChildren<InputType> ) => { const { embeddable, reducers, diffInput } = useMemo( () => ({ ...getDefaultProps<InputType>(), ...props }), @@ -98,6 +99,13 @@ export const ReduxEmbeddableWrapper = <InputType extends EmbeddableInput = Embed return; }, [embeddable]); + const ReduxEmbeddableStoreProvider = useMemo( + () => + ({ children }: PropsWithChildren<{}>) => + <Provider store={getManagedEmbeddablesStore()}>{children}</Provider>, + [] + ); + const reduxEmbeddableContext: ReduxEmbeddableContextServices | ReduxContainerContextServices = useMemo(() => { const key = `${embeddable.type}_${embeddable.id}`; @@ -145,19 +153,20 @@ export const ReduxEmbeddableWrapper = <InputType extends EmbeddableInput = Embed return { useEmbeddableDispatch: () => useDispatch<typeof store.dispatch>(), useEmbeddableSelector, + ReduxEmbeddableStoreProvider, actions: slice.actions as ReduxEmbeddableContextServices['actions'], containerActions, }; - }, [reducers, embeddable, containerActions]); + }, [reducers, embeddable, containerActions, ReduxEmbeddableStoreProvider]); return ( - <Provider store={getManagedEmbeddablesStore()}> + <ReduxEmbeddableStoreProvider> <ReduxEmbeddableContext.Provider value={reduxEmbeddableContext}> <ReduxEmbeddableSync diffInput={diffInput} embeddable={embeddable}> {props.children} </ReduxEmbeddableSync> </ReduxEmbeddableContext.Provider> - </Provider> + </ReduxEmbeddableStoreProvider> ); }; @@ -225,3 +234,9 @@ const ReduxEmbeddableSync = <InputType extends EmbeddableInput = EmbeddableInput return <>{children}</>; }; + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default ReduxEmbeddableWrapper; + +export type ReduxEmbeddableWrapperType = typeof ReduxEmbeddableWrapper; diff --git a/src/plugins/presentation_util/public/components/redux_embeddables/types.ts b/src/plugins/presentation_util/public/components/redux_embeddables/types.ts index 118b5d340528e..4fcc01ed51c48 100644 --- a/src/plugins/presentation_util/public/components/redux_embeddables/types.ts +++ b/src/plugins/presentation_util/public/components/redux_embeddables/types.ts @@ -13,6 +13,7 @@ import { Dispatch, PayloadAction, } from '@reduxjs/toolkit'; +import { PropsWithChildren } from 'react'; import { TypedUseSelectorHook } from 'react-redux'; import { EmbeddableInput, @@ -35,6 +36,10 @@ export interface ReduxEmbeddableWrapperProps<InputType extends EmbeddableInput = diffInput?: (a: InputType, b: InputType) => Partial<InputType>; } +export type ReduxEmbeddableWrapperPropsWithChildren< + InputType extends EmbeddableInput = EmbeddableInput +> = PropsWithChildren<ReduxEmbeddableWrapperProps<InputType>>; + /** * This context allows components underneath the redux embeddable wrapper to get access to the actions, selector, dispatch, and containerActions. */ @@ -47,6 +52,7 @@ export interface ReduxEmbeddableContextServices< Parameters<ReducerType[Property]>[1]['payload'] >; } & { updateEmbeddableReduxState: ActionCreatorWithPayload<Partial<InputType>> }; + ReduxEmbeddableStoreProvider: React.FC<PropsWithChildren<{}>>; useEmbeddableSelector: TypedUseSelectorHook<InputType>; useEmbeddableDispatch: () => Dispatch<AnyAction>; } diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index 6d83770499e78..7148b9fb6c7dd 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -41,6 +41,9 @@ export { LazyDashboardPicker, LazySavedObjectSaveModalDashboard, withSuspense, + LazyDataViewPicker, + LazyFieldPicker, + LazyReduxEmbeddableWrapper, } from './components'; export * from './components/types'; @@ -57,7 +60,13 @@ export { SolutionToolbarPopover, } from './components/solution_toolbar'; -export * from './components/controls'; +export { + ReduxEmbeddableContext, + useReduxContainerContext, + useReduxEmbeddableContext, + type ReduxContainerContextServices, + type ReduxEmbeddableWrapperPropsWithChildren, +} from './components/redux_embeddables'; /** * Register a set of Expression Functions with the Presentation Utility ExpressionInput. This allows diff --git a/src/plugins/presentation_util/public/mocks.ts b/src/plugins/presentation_util/public/mocks.ts index ec1c44d02c497..b569cb7436668 100644 --- a/src/plugins/presentation_util/public/mocks.ts +++ b/src/plugins/presentation_util/public/mocks.ts @@ -13,14 +13,11 @@ import { registry } from './services/kibana'; import { registerExpressionsLanguage } from '.'; const createStartContract = (coreStart: CoreStart): PresentationUtilPluginStart => { - pluginServices.setRegistry( - registry.start({ coreStart, startPlugins: { dataViews: {}, data: {} } as any }) - ); + pluginServices.setRegistry(registry.start({ coreStart, startPlugins: { dataViews: {} } as any })); const startContract: PresentationUtilPluginStart = { ContextProvider: pluginServices.getContextProvider(), labsService: pluginServices.getServices().labs, - controlsService: pluginServices.getServices().controls, registerExpressionsLanguage, }; return startContract; @@ -29,3 +26,5 @@ const createStartContract = (coreStart: CoreStart): PresentationUtilPluginStart export const presentationUtilPluginMock = { createStartContract, }; + +export * from './__stories__/fixtures/flights'; diff --git a/src/plugins/presentation_util/public/plugin.ts b/src/plugins/presentation_util/public/plugin.ts index 92802d0bc9934..9cd9027a53e76 100644 --- a/src/plugins/presentation_util/public/plugin.ts +++ b/src/plugins/presentation_util/public/plugin.ts @@ -12,16 +12,9 @@ import { registry } from './services/kibana'; import { PresentationUtilPluginSetupDeps, PresentationUtilPluginStartDeps, - ControlGroupContainerFactory, PresentationUtilPluginSetup, PresentationUtilPluginStart, - IEditableControlFactory, - ControlEditorProps, - ControlInput, - ControlEmbeddable, } from './types'; -import { OptionsListEmbeddableFactory } from './components/controls/control_types/options_list'; -import { CONTROL_GROUP_TYPE, OPTIONS_LIST_CONTROL } from '.'; import { registerExpressionsLanguage } from '.'; @@ -34,39 +27,10 @@ export class PresentationUtilPlugin PresentationUtilPluginStartDeps > { - private inlineEditors: { - [key: string]: { - controlEditorComponent?: (props: ControlEditorProps) => JSX.Element; - presaveTransformFunction?: ( - newInput: Partial<ControlInput>, - embeddable?: ControlEmbeddable - ) => Partial<ControlInput>; - }; - } = {}; - public setup( _coreSetup: CoreSetup<PresentationUtilPluginStartDeps, PresentationUtilPluginStart>, _setupPlugins: PresentationUtilPluginSetupDeps ): PresentationUtilPluginSetup { - _coreSetup.getStartServices().then(([coreStart, deps]) => { - // register control group embeddable factory - embeddable.registerEmbeddableFactory( - CONTROL_GROUP_TYPE, - new ControlGroupContainerFactory(deps.embeddable) - ); - }); - - const { embeddable } = _setupPlugins; - - // create control type embeddable factories. - const optionsListFactory = new OptionsListEmbeddableFactory(); - const editableOptionsListFactory = optionsListFactory as IEditableControlFactory; - this.inlineEditors[OPTIONS_LIST_CONTROL] = { - controlEditorComponent: editableOptionsListFactory.controlEditorComponent, - presaveTransformFunction: editableOptionsListFactory.presaveTransformFunction, - }; - embeddable.registerEmbeddableFactory(OPTIONS_LIST_CONTROL, optionsListFactory); - return {}; } @@ -75,25 +39,9 @@ export class PresentationUtilPlugin startPlugins: PresentationUtilPluginStartDeps ): PresentationUtilPluginStart { pluginServices.setRegistry(registry.start({ coreStart, startPlugins })); - const { controls: controlsService } = pluginServices.getServices(); - const { embeddable } = startPlugins; - - // register control types with controls service. - const optionsListFactory = embeddable.getEmbeddableFactory(OPTIONS_LIST_CONTROL); - // Temporarily pass along inline editors - inline editing should be made a first-class feature of embeddables - const editableOptionsListFactory = optionsListFactory as IEditableControlFactory; - const { - controlEditorComponent: optionsListControlEditor, - presaveTransformFunction: optionsListPresaveTransform, - } = this.inlineEditors[OPTIONS_LIST_CONTROL]; - editableOptionsListFactory.controlEditorComponent = optionsListControlEditor; - editableOptionsListFactory.presaveTransformFunction = optionsListPresaveTransform; - - if (optionsListFactory) controlsService.registerControlType(optionsListFactory); return { ContextProvider: pluginServices.getContextProvider(), - controlsService, labsService: pluginServices.getServices().labs, registerExpressionsLanguage, }; diff --git a/src/plugins/presentation_util/public/services/index.ts b/src/plugins/presentation_util/public/services/index.ts index d6112b86066e1..da840d7d24b2a 100644 --- a/src/plugins/presentation_util/public/services/index.ts +++ b/src/plugins/presentation_util/public/services/index.ts @@ -12,10 +12,7 @@ import { PresentationCapabilitiesService } from './capabilities'; import { PresentationDashboardsService } from './dashboards'; import { PresentationLabsService } from './labs'; import { registry as stubRegistry } from './stub'; -import { PresentationOverlaysService } from './overlays'; -import { PresentationControlsService } from './controls'; import { PresentationDataViewsService } from './data_views'; -import { PresentationDataService } from './data'; import { registerExpressionsLanguage } from '..'; export type { PresentationCapabilitiesService } from './capabilities'; @@ -25,10 +22,7 @@ export type { PresentationLabsService } from './labs'; export interface PresentationUtilServices { dashboards: PresentationDashboardsService; dataViews: PresentationDataViewsService; - data: PresentationDataService; capabilities: PresentationCapabilitiesService; - overlays: PresentationOverlaysService; - controls: PresentationControlsService; labs: PresentationLabsService; } @@ -39,7 +33,6 @@ export const getStubPluginServices = (): PresentationUtilPluginStart => { return { ContextProvider: pluginServices.getContextProvider(), labsService: pluginServices.getServices().labs, - controlsService: pluginServices.getServices().controls, registerExpressionsLanguage, }; }; diff --git a/src/plugins/presentation_util/public/services/kibana/index.ts b/src/plugins/presentation_util/public/services/kibana/index.ts index 3820442555c26..e412ca5ab1b48 100644 --- a/src/plugins/presentation_util/public/services/kibana/index.ts +++ b/src/plugins/presentation_util/public/services/kibana/index.ts @@ -18,9 +18,6 @@ import { PresentationUtilServices } from '..'; import { capabilitiesServiceFactory } from './capabilities'; import { dataViewsServiceFactory } from './data_views'; import { dashboardsServiceFactory } from './dashboards'; -import { controlsServiceFactory } from './controls'; -import { overlaysServiceFactory } from './overlays'; -import { dataServiceFactory } from './data'; import { labsServiceFactory } from './labs'; export const providers: PluginServiceProviders< @@ -30,10 +27,7 @@ export const providers: PluginServiceProviders< capabilities: new PluginServiceProvider(capabilitiesServiceFactory), labs: new PluginServiceProvider(labsServiceFactory), dataViews: new PluginServiceProvider(dataViewsServiceFactory), - data: new PluginServiceProvider(dataServiceFactory), dashboards: new PluginServiceProvider(dashboardsServiceFactory), - overlays: new PluginServiceProvider(overlaysServiceFactory), - controls: new PluginServiceProvider(controlsServiceFactory), }; export const registry = new PluginServiceRegistry< diff --git a/src/plugins/presentation_util/public/services/storybook/index.ts b/src/plugins/presentation_util/public/services/storybook/index.ts index a2d729f6d730a..18333bd5522ca 100644 --- a/src/plugins/presentation_util/public/services/storybook/index.ts +++ b/src/plugins/presentation_util/public/services/storybook/index.ts @@ -16,10 +16,7 @@ import { dashboardsServiceFactory } from '../stub/dashboards'; import { labsServiceFactory } from './labs'; import { capabilitiesServiceFactory } from './capabilities'; import { PresentationUtilServices } from '..'; -import { overlaysServiceFactory } from './overlays'; -import { controlsServiceFactory } from './controls'; import { dataViewsServiceFactory } from './data_views'; -import { dataServiceFactory } from './data'; export type { PluginServiceProviders } from '../create'; export { PluginServiceProvider, PluginServiceRegistry } from '../create'; @@ -36,9 +33,6 @@ export const providers: PluginServiceProviders<PresentationUtilServices, Storybo capabilities: new PluginServiceProvider(capabilitiesServiceFactory), dashboards: new PluginServiceProvider(dashboardsServiceFactory), dataViews: new PluginServiceProvider(dataViewsServiceFactory), - data: new PluginServiceProvider(dataServiceFactory), - overlays: new PluginServiceProvider(overlaysServiceFactory), - controls: new PluginServiceProvider(controlsServiceFactory), labs: new PluginServiceProvider(labsServiceFactory), }; diff --git a/src/plugins/presentation_util/public/services/stub/index.ts b/src/plugins/presentation_util/public/services/stub/index.ts index 2e312ff682927..34a13d030e535 100644 --- a/src/plugins/presentation_util/public/services/stub/index.ts +++ b/src/plugins/presentation_util/public/services/stub/index.ts @@ -11,21 +11,15 @@ import { dashboardsServiceFactory } from './dashboards'; import { labsServiceFactory } from './labs'; import { PluginServiceProviders, PluginServiceProvider, PluginServiceRegistry } from '../create'; import { PresentationUtilServices } from '..'; -import { overlaysServiceFactory } from './overlays'; -import { controlsServiceFactory } from './controls'; export { dashboardsServiceFactory } from './dashboards'; export { capabilitiesServiceFactory } from './capabilities'; -import { dataServiceFactory } from '../storybook/data'; import { dataViewsServiceFactory } from '../storybook/data_views'; export const providers: PluginServiceProviders<PresentationUtilServices> = { dashboards: new PluginServiceProvider(dashboardsServiceFactory), capabilities: new PluginServiceProvider(capabilitiesServiceFactory), - overlays: new PluginServiceProvider(overlaysServiceFactory), - controls: new PluginServiceProvider(controlsServiceFactory), labs: new PluginServiceProvider(labsServiceFactory), - data: new PluginServiceProvider(dataServiceFactory), dataViews: new PluginServiceProvider(dataViewsServiceFactory), }; diff --git a/src/plugins/presentation_util/public/types.ts b/src/plugins/presentation_util/public/types.ts index 3717cf2505dd8..a918ee3adaf35 100644 --- a/src/plugins/presentation_util/public/types.ts +++ b/src/plugins/presentation_util/public/types.ts @@ -6,12 +6,9 @@ * Side Public License, v 1. */ -import { DataPublicPluginStart } from '../../data/public'; +import { registerExpressionsLanguage } from '.'; import { PresentationLabsService } from './services/labs'; -import { PresentationControlsService } from './services/controls'; import { DataViewsPublicPluginStart } from '../../data_views/public'; -import { EmbeddableSetup, EmbeddableStart } from '../../embeddable/public'; -import { registerExpressionsLanguage } from '.'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PresentationUtilPluginSetup {} @@ -19,17 +16,11 @@ export interface PresentationUtilPluginSetup {} export interface PresentationUtilPluginStart { ContextProvider: React.FC; labsService: PresentationLabsService; - controlsService: PresentationControlsService; registerExpressionsLanguage: typeof registerExpressionsLanguage; } +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PresentationUtilPluginSetupDeps {} -export interface PresentationUtilPluginSetupDeps { - embeddable: EmbeddableSetup; -} export interface PresentationUtilPluginStartDeps { - data: DataPublicPluginStart; - embeddable: EmbeddableStart; dataViews: DataViewsPublicPluginStart; } - -export * from './components/controls'; diff --git a/src/plugins/presentation_util/server/plugin.ts b/src/plugins/presentation_util/server/plugin.ts index 2c52fa1f6c2d8..eb55373920625 100644 --- a/src/plugins/presentation_util/server/plugin.ts +++ b/src/plugins/presentation_util/server/plugin.ts @@ -7,24 +7,11 @@ */ import { CoreSetup, Plugin } from 'kibana/server'; -import { EmbeddableSetup } from '../../embeddable/server'; -import { controlGroupContainerPersistableStateServiceFactory } from './controls/control_group/control_group_container_factory'; -import { optionsListPersistableStateServiceFactory } from './controls/control_types/options_list/options_list_embeddable_factory'; import { getUISettings } from './ui_settings'; -interface SetupDeps { - embeddable: EmbeddableSetup; -} - -export class PresentationUtilPlugin implements Plugin<object, object, SetupDeps> { - public setup(core: CoreSetup, plugins: SetupDeps) { +export class PresentationUtilPlugin implements Plugin<object, object> { + public setup(core: CoreSetup) { core.uiSettings.register(getUISettings()); - - plugins.embeddable.registerEmbeddableFactory(optionsListPersistableStateServiceFactory()); - - plugins.embeddable.registerEmbeddableFactory( - controlGroupContainerPersistableStateServiceFactory(plugins.embeddable) - ); return {}; } diff --git a/src/plugins/presentation_util/tsconfig.json b/src/plugins/presentation_util/tsconfig.json index caabd0b18af71..38f2cf3c14a12 100644 --- a/src/plugins/presentation_util/tsconfig.json +++ b/src/plugins/presentation_util/tsconfig.json @@ -6,9 +6,7 @@ "declaration": true, "declarationMap": true }, - "extraPublicDirs": [ - "common" - ], + "extraPublicDirs": ["common"], "include": [ "common/**/*", "public/**/*", @@ -20,9 +18,8 @@ "references": [ { "path": "../../core/tsconfig.json" }, { "path": "../saved_objects/tsconfig.json" }, - { "path": "../kibana_react/tsconfig.json" }, { "path": "../embeddable/tsconfig.json" }, - { "path": "../kibana_react/tsconfig.json"}, + { "path": "../kibana_react/tsconfig.json" }, { "path": "../data/tsconfig.json" } ] } diff --git a/test/functional/page_objects/dashboard_page_controls.ts b/test/functional/page_objects/dashboard_page_controls.ts index 2603608eebee9..1adc60b3596b6 100644 --- a/test/functional/page_objects/dashboard_page_controls.ts +++ b/test/functional/page_objects/dashboard_page_controls.ts @@ -7,8 +7,7 @@ */ import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; -import { OPTIONS_LIST_CONTROL } from '../../../src/plugins/presentation_util/common/controls/'; -import { ControlWidth } from '../../../src/plugins/presentation_util/public/components/controls'; +import { OPTIONS_LIST_CONTROL, ControlWidth } from '../../../src/plugins/controls/common'; import { FtrService } from '../ftr_provider_context'; diff --git a/x-pack/plugins/canvas/public/functions/pie.test.js b/x-pack/plugins/canvas/public/functions/pie.test.js index ef180181701c9..6f1b66018fb49 100644 --- a/x-pack/plugins/canvas/public/functions/pie.test.js +++ b/x-pack/plugins/canvas/public/functions/pie.test.js @@ -6,7 +6,7 @@ */ import { testPie } from '../../canvas_plugin_src/functions/common/__fixtures__/test_pointseries'; -import { functionWrapper, fontStyle } from '../../../../../src/plugins/presentation_util/public'; +import { fontStyle, functionWrapper } from '../../../../../src/plugins/presentation_util/public'; import { grayscalePalette, seriesStyle, diff --git a/x-pack/plugins/canvas/public/functions/plot.test.js b/x-pack/plugins/canvas/public/functions/plot.test.js index b354c4c02b2f6..1f74698ccaf4e 100644 --- a/x-pack/plugins/canvas/public/functions/plot.test.js +++ b/x-pack/plugins/canvas/public/functions/plot.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { functionWrapper, fontStyle } from '../../../../../src/plugins/presentation_util/public'; +import { fontStyle, functionWrapper } from '../../../../../src/plugins/presentation_util/public'; import { testPlot } from '../../canvas_plugin_src/functions/common/__fixtures__/test_pointseries'; import { grayscalePalette,