[Control Group] Add Button & Minimal Editor Settings (#151161)

Adds an "add" button that shows up to the right of the Control Group if
configured. Also adds a system for fetching settings from consumers, and
adds settings that can hide or show pieces of the Control editor flyout.
This commit is contained in:
Devon Thomson 2023-02-23 13:45:20 -06:00 committed by GitHub
parent fa6c0d1c31
commit f77f924a2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 346 additions and 197 deletions

View file

@ -0,0 +1,70 @@
/*
* 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 { ViewMode } from '@kbn/embeddable-plugin/public';
import { withSuspense } from '@kbn/presentation-util-plugin/public';
import { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { LazyControlGroupRenderer } from '@kbn/controls-plugin/public';
const ControlGroupRenderer = withSuspense(LazyControlGroupRenderer);
export const AddButtonExample = ({ dataViewId }: { dataViewId: string }) => {
return (
<>
<EuiTitle>
<h2>Add button example</h2>
</EuiTitle>
<EuiText>
<p>
Use the built in add button to add controls to a control group based on a hardcoded
dataViewId and a simplified editor flyout
</p>
</EuiText>
<EuiSpacer size="m" />
<EuiPanel hasBorder={true}>
<ControlGroupRenderer
getCreationOptions={async (initialInput, builder) => {
await builder.addDataControlFromField(initialInput, {
dataViewId,
title: 'Destintion',
fieldName: 'geo.dest',
grow: false,
width: 'small',
});
await builder.addDataControlFromField(initialInput, {
dataViewId,
fieldName: 'geo.src',
grow: false,
title: 'Source',
width: 'small',
});
return {
initialInput: {
...initialInput,
viewMode: ViewMode.EDIT,
defaultControlGrow: false,
defaultControlWidth: 'small',
},
settings: {
showAddButton: true,
staticDataViewId: dataViewId,
editorConfig: {
hideAdditionalSettings: true,
hideDataViewSelector: true,
hideWidthSettings: true,
},
},
};
}}
/>
</EuiPanel>
</>
);
};

View file

@ -16,6 +16,7 @@ import { ControlsExampleStartDeps } from './plugin';
import { BasicReduxExample } from './basic_redux_example'; import { BasicReduxExample } from './basic_redux_example';
import { EditExample } from './edit_example'; import { EditExample } from './edit_example';
import { SearchExample } from './search_example'; import { SearchExample } from './search_example';
import { AddButtonExample } from './add_button_example';
export const renderApp = async ( export const renderApp = async (
{ data, navigation }: ControlsExampleStartDeps, { data, navigation }: ControlsExampleStartDeps,
@ -30,6 +31,8 @@ export const renderApp = async (
<EditExample /> <EditExample />
<EuiSpacer size="xl" /> <EuiSpacer size="xl" />
<BasicReduxExample dataViewId={dataViews[0].id!} /> <BasicReduxExample dataViewId={dataViews[0].id!} />
<EuiSpacer size="xl" />
<AddButtonExample dataViewId={dataViews[0].id!} />
</> </>
) : ( ) : (
<div>{'Install web logs sample data to run controls examples.'}</div> <div>{'Install web logs sample data to run controls examples.'}</div>

View file

@ -80,7 +80,7 @@ export const BasicReduxExample = ({ dataViewId }: { dataViewId: string }) => {
onLoadComplete={async (newControlGroup) => { onLoadComplete={async (newControlGroup) => {
setControlGroup(newControlGroup); setControlGroup(newControlGroup);
}} }}
getInitialInput={async (initialInput, builder) => { getCreationOptions={async (initialInput, builder) => {
await builder.addDataControlFromField(initialInput, { await builder.addDataControlFromField(initialInput, {
dataViewId, dataViewId,
title: 'Destintion country', title: 'Destintion country',
@ -96,8 +96,10 @@ export const BasicReduxExample = ({ dataViewId }: { dataViewId: string }) => {
title: 'Bytes', title: 'Bytes',
}); });
return { return {
...initialInput, initialInput: {
viewMode: ViewMode.VIEW, ...initialInput,
viewMode: ViewMode.VIEW,
},
}; };
}} }}
/> />

View file

@ -104,12 +104,14 @@ export const EditExample = () => {
</> </>
) : null} ) : null}
<ControlGroupRenderer <ControlGroupRenderer
getInitialInput={async (initialInput, builder) => { getCreationOptions={async (initialInput, builder) => {
const persistedInput = await onLoad(); const persistedInput = await onLoad();
return { return {
...initialInput, initialInput: {
...persistedInput, ...initialInput,
viewMode: ViewMode.EDIT, ...persistedInput,
viewMode: ViewMode.EDIT,
},
}; };
}} }}
onLoadComplete={async (newControlGroup) => { onLoadComplete={async (newControlGroup) => {

View file

@ -133,7 +133,7 @@ export const SearchExample = ({ data, dataView, navigation }: Props) => {
/> />
<ControlGroupRenderer <ControlGroupRenderer
filters={filters} filters={filters}
getInitialInput={async (initialInput, builder) => { getCreationOptions={async (initialInput, builder) => {
await builder.addDataControlFromField(initialInput, { await builder.addDataControlFromField(initialInput, {
dataViewId: dataView.id!, dataViewId: dataView.id!,
title: 'Destintion country', title: 'Destintion country',
@ -149,8 +149,10 @@ export const SearchExample = ({ data, dataView, navigation }: Props) => {
title: 'Bytes', title: 'Bytes',
}); });
return { return {
...initialInput, initialInput: {
viewMode: ViewMode.VIEW, ...initialInput,
viewMode: ViewMode.VIEW,
},
}; };
}} }}
onLoadComplete={async (newControlGroup) => { onLoadComplete={async (newControlGroup) => {

View file

@ -8,7 +8,7 @@
import '../control_group.scss'; import '../control_group.scss';
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
@ -30,19 +30,15 @@ import {
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { ViewMode } from '@kbn/embeddable-plugin/public'; import { ViewMode } from '@kbn/embeddable-plugin/public';
import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public';
import { ControlGroupReduxState } from '../types';
import { controlGroupReducers } from '../state/control_group_reducers';
import { ControlClone, SortableControl } from './control_group_sortable_item'; import { ControlClone, SortableControl } from './control_group_sortable_item';
import { useControlGroupContainerContext } from '../control_group_renderer';
import { ControlGroupStrings } from '../control_group_strings';
export const ControlGroup = () => { export const ControlGroup = () => {
// Redux embeddable container Context // Redux embeddable container Context
const reduxContext = useReduxEmbeddableContext< const reduxContext = useControlGroupContainerContext();
ControlGroupReduxState,
typeof controlGroupReducers
>();
const { const {
embeddableInstance: controlGroup,
actions: { setControlOrders }, actions: { setControlOrders },
useEmbeddableSelector: select, useEmbeddableSelector: select,
useEmbeddableDispatch, useEmbeddableDispatch,
@ -53,6 +49,7 @@ export const ControlGroup = () => {
const panels = select((state) => state.explicitInput.panels); const panels = select((state) => state.explicitInput.panels);
const viewMode = select((state) => state.explicitInput.viewMode); const viewMode = select((state) => state.explicitInput.viewMode);
const controlStyle = select((state) => state.explicitInput.controlStyle); const controlStyle = select((state) => state.explicitInput.controlStyle);
const showAddButton = select((state) => state.componentState.showAddButton);
const isEditable = viewMode === ViewMode.EDIT; const isEditable = viewMode === ViewMode.EDIT;
@ -101,7 +98,7 @@ export const ControlGroup = () => {
return ( return (
<> <>
{idsInOrder.length > 0 ? ( {idsInOrder.length > 0 || showAddButton ? (
<EuiPanel <EuiPanel
borderRadius="m" borderRadius="m"
color={panelBg} color={panelBg}
@ -159,6 +156,18 @@ export const ControlGroup = () => {
</DragOverlay> </DragOverlay>
</DndContext> </DndContext>
</EuiFlexItem> </EuiFlexItem>
{showAddButton && (
<EuiFlexItem grow={false}>
<EuiButtonIcon
size="s"
iconSize="m"
display="base"
iconType={'plusInCircle'}
aria-label={ControlGroupStrings.management.getAddControlTitle()}
onClick={() => controlGroup.openAddDataControlFlyout()}
/>
</EuiFlexItem>
)}
</EuiFlexGroup> </EuiFlexGroup>
</EuiPanel> </EuiPanel>
) : ( ) : (

View file

@ -11,29 +11,31 @@ import { isEqual } from 'lodash';
import useLifecycles from 'react-use/lib/useLifecycles'; import useLifecycles from 'react-use/lib/useLifecycles';
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { IEmbeddable } from '@kbn/embeddable-plugin/public';
import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public';
import type { Filter, TimeRange, Query } from '@kbn/es-query';
import { compareFilters } from '@kbn/es-query'; import { compareFilters } from '@kbn/es-query';
import type { Filter, TimeRange, Query } from '@kbn/es-query';
import { EmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public';
import { pluginServices } from '../services';
import { getDefaultControlGroupInput } from '../../common';
import { import {
ControlGroupCreationOptions,
ControlGroupInput, ControlGroupInput,
ControlGroupOutput, ControlGroupOutput,
ControlGroupReduxState, ControlGroupReduxState,
CONTROL_GROUP_TYPE, CONTROL_GROUP_TYPE,
} from './types'; } from './types';
import { ControlGroupContainer } from './embeddable/control_group_container'; import { pluginServices } from '../services';
import { getDefaultControlGroupInput } from '../../common';
import { controlGroupReducers } from './state/control_group_reducers'; import { controlGroupReducers } from './state/control_group_reducers';
import { controlGroupInputBuilder } from './control_group_input_builder'; import { controlGroupInputBuilder } from './control_group_input_builder';
import { ControlGroupContainer } from './embeddable/control_group_container';
import { ControlGroupContainerFactory } from './embeddable/control_group_container_factory';
export interface ControlGroupRendererProps { export interface ControlGroupRendererProps {
filters?: Filter[]; filters?: Filter[];
getInitialInput: ( getCreationOptions: (
initialInput: Partial<ControlGroupInput>, initialInput: Partial<ControlGroupInput>,
builder: typeof controlGroupInputBuilder builder: typeof controlGroupInputBuilder
) => Promise<Partial<ControlGroupInput>>; ) => Promise<ControlGroupCreationOptions>;
onLoadComplete?: (controlGroup: ControlGroupContainer) => void; onLoadComplete?: (controlGroup: ControlGroupContainer) => void;
timeRange?: TimeRange; timeRange?: TimeRange;
query?: Query; query?: Query;
@ -41,7 +43,7 @@ export interface ControlGroupRendererProps {
export const ControlGroupRenderer = ({ export const ControlGroupRenderer = ({
onLoadComplete, onLoadComplete,
getInitialInput, getCreationOptions,
filters, filters,
timeRange, timeRange,
query, query,
@ -57,16 +59,26 @@ export const ControlGroupRenderer = ({
() => { () => {
const { embeddable } = pluginServices.getServices(); const { embeddable } = pluginServices.getServices();
(async () => { (async () => {
const factory = embeddable.getEmbeddableFactory< const factory = embeddable.getEmbeddableFactory(CONTROL_GROUP_TYPE) as EmbeddableFactory<
ControlGroupInput, ControlGroupInput,
ControlGroupOutput, ControlGroupOutput,
IEmbeddable<ControlGroupInput, ControlGroupOutput> ControlGroupContainer
>(CONTROL_GROUP_TYPE); > & {
const newControlGroup = (await factory?.create({ create: ControlGroupContainerFactory['create'];
id, };
...getDefaultControlGroupInput(), const { initialInput, settings } = await getCreationOptions(
...(await getInitialInput(getDefaultControlGroupInput(), controlGroupInputBuilder)), getDefaultControlGroupInput(),
})) as ControlGroupContainer; controlGroupInputBuilder
);
const newControlGroup = (await factory?.create(
{
id,
...getDefaultControlGroupInput(),
...initialInput,
},
undefined,
settings
)) as ControlGroupContainer;
if (controlGroupRef.current) { if (controlGroupRef.current) {
newControlGroup.render(controlGroupRef.current); newControlGroup.render(controlGroupRef.current);
@ -105,7 +117,11 @@ export const ControlGroupRenderer = ({
}; };
export const useControlGroupContainerContext = () => export const useControlGroupContainerContext = () =>
useReduxEmbeddableContext<ControlGroupReduxState, typeof controlGroupReducers>(); useReduxEmbeddableContext<
ControlGroupReduxState,
typeof controlGroupReducers,
ControlGroupContainer
>();
// required for dynamic import using React.lazy() // required for dynamic import using React.lazy()
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export

View file

@ -53,6 +53,7 @@ import {
import { CONTROL_WIDTH_OPTIONS } from './editor_constants'; import { CONTROL_WIDTH_OPTIONS } from './editor_constants';
import { pluginServices } from '../../services'; import { pluginServices } from '../../services';
import { getDataControlFieldRegistry } from './data_control_editor_tools'; import { getDataControlFieldRegistry } from './data_control_editor_tools';
import { useControlGroupContainerContext } from '../control_group_renderer';
interface EditControlProps { interface EditControlProps {
embeddable?: ControlEmbeddable<DataControlInput>; embeddable?: ControlEmbeddable<DataControlInput>;
isCreate: boolean; isCreate: boolean;
@ -99,6 +100,10 @@ export const ControlEditor = ({
dataViews: { getIdsWithTitle, getDefaultId, get }, dataViews: { getIdsWithTitle, getDefaultId, get },
controls: { getControlFactory }, controls: { getControlFactory },
} = pluginServices.getServices(); } = pluginServices.getServices();
const { useEmbeddableSelector: select } = useControlGroupContainerContext();
const editorConfig = select((state) => state.componentState.editorConfig);
const [state, setState] = useState<ControlEditorState>({ const [state, setState] = useState<ControlEditorState>({
dataViewListItems: [], dataViewListItems: [],
}); });
@ -170,27 +175,29 @@ export const ControlEditor = ({
</EuiFlyoutHeader> </EuiFlyoutHeader>
<EuiFlyoutBody data-test-subj="control-editor-flyout"> <EuiFlyoutBody data-test-subj="control-editor-flyout">
<EuiForm> <EuiForm>
<EuiFormRow label={ControlGroupStrings.manageControl.getDataViewTitle()}> {!editorConfig?.hideDataViewSelector && (
<DataViewPicker <EuiFormRow label={ControlGroupStrings.manageControl.getDataViewTitle()}>
dataViews={state.dataViewListItems} <DataViewPicker
selectedDataViewId={dataView?.id} dataViews={state.dataViewListItems}
onChangeDataViewId={(dataViewId) => { selectedDataViewId={dataView?.id}
setLastUsedDataViewId?.(dataViewId); onChangeDataViewId={(dataViewId) => {
if (dataViewId === dataView?.id) return; setLastUsedDataViewId?.(dataViewId);
if (dataViewId === dataView?.id) return;
onTypeEditorChange({ dataViewId }); onTypeEditorChange({ dataViewId });
setSelectedField(undefined); setSelectedField(undefined);
get(dataViewId).then((newDataView) => { get(dataViewId).then((newDataView) => {
setState((s) => ({ ...s, selectedDataView: newDataView })); setState((s) => ({ ...s, selectedDataView: newDataView }));
}); });
}} }}
trigger={{ trigger={{
label: label:
state.selectedDataView?.getName() ?? state.selectedDataView?.getName() ??
ControlGroupStrings.manageControl.getSelectDataViewMessage(), ControlGroupStrings.manageControl.getSelectDataViewMessage(),
}} }}
/> />
</EuiFormRow> </EuiFormRow>
)}
<EuiFormRow label={ControlGroupStrings.manageControl.getFieldTitle()}> <EuiFormRow label={ControlGroupStrings.manageControl.getFieldTitle()}>
<FieldPicker <FieldPicker
filterPredicate={(field: DataViewField) => { filterPredicate={(field: DataViewField) => {
@ -239,44 +246,48 @@ export const ControlEditor = ({
}} }}
/> />
</EuiFormRow> </EuiFormRow>
<EuiFormRow label={ControlGroupStrings.manageControl.getWidthInputTitle()}> {!editorConfig?.hideWidthSettings && (
<> <EuiFormRow label={ControlGroupStrings.manageControl.getWidthInputTitle()}>
<EuiButtonGroup <>
color="primary" <EuiButtonGroup
legend={ControlGroupStrings.management.controlWidth.getWidthSwitchLegend()} color="primary"
options={CONTROL_WIDTH_OPTIONS} legend={ControlGroupStrings.management.controlWidth.getWidthSwitchLegend()}
idSelected={currentWidth} options={CONTROL_WIDTH_OPTIONS}
onChange={(newWidth: string) => { idSelected={currentWidth}
setCurrentWidth(newWidth as ControlWidth); onChange={(newWidth: string) => {
updateWidth(newWidth as ControlWidth); setCurrentWidth(newWidth as ControlWidth);
}} updateWidth(newWidth as ControlWidth);
/> }}
{updateGrow && ( />
<> {updateGrow && (
<EuiSpacer size="s" /> <>
<EuiSwitch <EuiSpacer size="s" />
label={ControlGroupStrings.manageControl.getGrowSwitchTitle()} <EuiSwitch
color="primary" label={ControlGroupStrings.manageControl.getGrowSwitchTitle()}
checked={currentGrow} color="primary"
onChange={() => { checked={currentGrow}
setCurrentGrow(!currentGrow); onChange={() => {
updateGrow(!currentGrow); setCurrentGrow(!currentGrow);
}} updateGrow(!currentGrow);
data-test-subj="control-editor-grow-switch" }}
/> data-test-subj="control-editor-grow-switch"
</> />
)} </>
</> )}
</EuiFormRow> </>
{CustomSettings && (factory as IEditableControlFactory).controlEditorOptionsComponent && (
<EuiFormRow label={ControlGroupStrings.manageControl.getControlSettingsTitle()}>
<CustomSettings
onChange={onTypeEditorChange}
initialInput={embeddable?.getInput()}
fieldType={fieldRegistry[selectedField].field.type}
/>
</EuiFormRow> </EuiFormRow>
)} )}
{!editorConfig?.hideAdditionalSettings &&
CustomSettings &&
(factory as IEditableControlFactory).controlEditorOptionsComponent && (
<EuiFormRow label={ControlGroupStrings.manageControl.getControlSettingsTitle()}>
<CustomSettings
onChange={onTypeEditorChange}
initialInput={embeddable?.getInput()}
fieldType={fieldRegistry[selectedField].field.type}
/>
</EuiFormRow>
)}
{removeControl && ( {removeControl && (
<> <>
<EuiSpacer size="l" /> <EuiSpacer size="l" />

View file

@ -13,19 +13,18 @@ import React, { useEffect, useRef } from 'react';
import { OverlayRef } from '@kbn/core/public'; import { OverlayRef } from '@kbn/core/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { EmbeddableFactoryNotFoundError } from '@kbn/embeddable-plugin/public'; import { EmbeddableFactoryNotFoundError } from '@kbn/embeddable-plugin/public';
import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public';
import { ControlGroupReduxState } from '../types';
import { ControlEditor } from './control_editor';
import { pluginServices } from '../../services';
import { ControlGroupStrings } from '../control_group_strings';
import { import {
IEditableControlFactory,
ControlInput, ControlInput,
DataControlInput, DataControlInput,
ControlEmbeddable, ControlEmbeddable,
IEditableControlFactory,
} from '../../types'; } from '../../types';
import { controlGroupReducers } from '../state/control_group_reducers'; import { pluginServices } from '../../services';
import { ControlGroupContainer, setFlyoutRef } from '../embeddable/control_group_container'; import { ControlEditor } from './control_editor';
import { ControlGroupStrings } from '../control_group_strings';
import { setFlyoutRef } from '../embeddable/control_group_container';
import { useControlGroupContainerContext } from '../control_group_renderer';
interface EditControlResult { interface EditControlResult {
type: string; type: string;
@ -40,11 +39,7 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) =>
theme: { theme$ }, theme: { theme$ },
} = pluginServices.getServices(); } = pluginServices.getServices();
// Redux embeddable container Context // Redux embeddable container Context
const reduxContext = useReduxEmbeddableContext< const reduxContext = useControlGroupContainerContext();
ControlGroupReduxState,
typeof controlGroupReducers,
ControlGroupContainer
>();
const { const {
embeddableInstance: controlGroup, embeddableInstance: controlGroup,
actions: { setControlWidth, setControlGrow }, actions: { setControlWidth, setControlGrow },
@ -126,41 +121,45 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) =>
ref.close(); ref.close();
}; };
const ReduxWrapper = controlGroup.getReduxEmbeddableTools().Wrapper;
const flyoutInstance = openFlyout( const flyoutInstance = openFlyout(
toMountPoint( toMountPoint(
<ControlsServicesProvider> <ControlsServicesProvider>
<ControlEditor <ReduxWrapper>
isCreate={false} <ControlEditor
width={panel.width} isCreate={false}
grow={panel.grow} width={panel.width}
embeddable={embeddable} grow={panel.grow}
title={embeddable.getTitle()} embeddable={embeddable}
onCancel={() => onCancel(flyoutInstance)} title={embeddable.getTitle()}
updateTitle={(newTitle) => (inputToReturn.title = newTitle)} onCancel={() => onCancel(flyoutInstance)}
setLastUsedDataViewId={(lastUsed) => controlGroup.setLastUsedDataViewId(lastUsed)} updateTitle={(newTitle) => (inputToReturn.title = newTitle)}
updateWidth={(newWidth) => setLastUsedDataViewId={(lastUsed) => controlGroup.setLastUsedDataViewId(lastUsed)}
dispatch(setControlWidth({ width: newWidth, embeddableId })) updateWidth={(newWidth) =>
} dispatch(setControlWidth({ width: newWidth, embeddableId }))
updateGrow={(grow) => dispatch(setControlGrow({ grow, embeddableId }))} }
onTypeEditorChange={(partialInput) => { updateGrow={(grow) => dispatch(setControlGrow({ grow, embeddableId }))}
inputToReturn = { ...inputToReturn, ...partialInput }; onTypeEditorChange={(partialInput) => {
}} inputToReturn = { ...inputToReturn, ...partialInput };
onSave={(type) => onSave(flyoutInstance, type)} }}
removeControl={() => { onSave={(type) => onSave(flyoutInstance, type)}
openConfirm(ControlGroupStrings.management.deleteControls.getSubtitle(), { removeControl={() => {
confirmButtonText: ControlGroupStrings.management.deleteControls.getConfirm(), openConfirm(ControlGroupStrings.management.deleteControls.getSubtitle(), {
cancelButtonText: ControlGroupStrings.management.deleteControls.getCancel(), confirmButtonText: ControlGroupStrings.management.deleteControls.getConfirm(),
title: ControlGroupStrings.management.deleteControls.getDeleteTitle(), cancelButtonText: ControlGroupStrings.management.deleteControls.getCancel(),
buttonColor: 'danger', title: ControlGroupStrings.management.deleteControls.getDeleteTitle(),
}).then((confirmed) => { buttonColor: 'danger',
if (confirmed) { }).then((confirmed) => {
controlGroup.removeEmbeddable(embeddableId); if (confirmed) {
removed = true; controlGroup.removeEmbeddable(embeddableId);
flyoutInstance.close(); removed = true;
} flyoutInstance.close();
}); }
}} });
/> }}
/>
</ReduxWrapper>
</ControlsServicesProvider>, </ControlsServicesProvider>,
{ theme$ } { theme$ }
), ),

View file

@ -31,6 +31,7 @@ export function openAddDataControlFlyout(this: ControlGroupContainer) {
theme: { theme$ }, theme: { theme$ },
} = pluginServices.getServices(); } = pluginServices.getServices();
const ControlsServicesProvider = pluginServices.getContextProvider(); const ControlsServicesProvider = pluginServices.getContextProvider();
const ReduxWrapper = this.getReduxEmbeddableTools().Wrapper;
let controlInput: Partial<DataControlInput> = {}; let controlInput: Partial<DataControlInput> = {};
const onCancel = () => { const onCancel = () => {
@ -54,43 +55,45 @@ export function openAddDataControlFlyout(this: ControlGroupContainer) {
const flyoutInstance = openFlyout( const flyoutInstance = openFlyout(
toMountPoint( toMountPoint(
<ControlsServicesProvider> <ControlsServicesProvider>
<ControlEditor <ReduxWrapper>
setLastUsedDataViewId={(newId) => this.setLastUsedDataViewId(newId)} <ControlEditor
getRelevantDataViewId={this.getMostRelevantDataViewId} setLastUsedDataViewId={(newId) => this.setLastUsedDataViewId(newId)}
isCreate={true} getRelevantDataViewId={this.getMostRelevantDataViewId}
width={this.getInput().defaultControlWidth ?? DEFAULT_CONTROL_WIDTH} isCreate={true}
grow={this.getInput().defaultControlGrow ?? DEFAULT_CONTROL_GROW} width={this.getInput().defaultControlWidth ?? DEFAULT_CONTROL_WIDTH}
updateTitle={(newTitle) => (controlInput.title = newTitle)} grow={this.getInput().defaultControlGrow ?? DEFAULT_CONTROL_GROW}
updateWidth={(defaultControlWidth) => this.updateInput({ defaultControlWidth })} updateTitle={(newTitle) => (controlInput.title = newTitle)}
updateGrow={(defaultControlGrow: boolean) => this.updateInput({ defaultControlGrow })} updateWidth={(defaultControlWidth) => this.updateInput({ defaultControlWidth })}
onSave={(type) => { updateGrow={(defaultControlGrow: boolean) => this.updateInput({ defaultControlGrow })}
this.closeAllFlyouts(); onSave={(type) => {
if (!type) { this.closeAllFlyouts();
return; if (!type) {
} return;
}
const factory = getControlFactory(type) as IEditableControlFactory; const factory = getControlFactory(type) as IEditableControlFactory;
if (factory.presaveTransformFunction) { if (factory.presaveTransformFunction) {
controlInput = factory.presaveTransformFunction(controlInput); controlInput = factory.presaveTransformFunction(controlInput);
} }
if (type === OPTIONS_LIST_CONTROL) { if (type === OPTIONS_LIST_CONTROL) {
this.addOptionsListControl(controlInput as AddOptionsListControlProps); this.addOptionsListControl(controlInput as AddOptionsListControlProps);
return; return;
} }
if (type === RANGE_SLIDER_CONTROL) { if (type === RANGE_SLIDER_CONTROL) {
this.addRangeSliderControl(controlInput as AddRangeSliderControlProps); this.addRangeSliderControl(controlInput as AddRangeSliderControlProps);
return; return;
} }
this.addDataControlFromField(controlInput as AddDataControlProps); this.addDataControlFromField(controlInput as AddDataControlProps);
}} }}
onCancel={onCancel} onCancel={onCancel}
onTypeEditorChange={(partialInput) => onTypeEditorChange={(partialInput) =>
(controlInput = { ...controlInput, ...partialInput }) (controlInput = { ...controlInput, ...partialInput })
} }
/> />
</ReduxWrapper>
</ControlsServicesProvider>, </ControlsServicesProvider>,
{ theme$ } { theme$ }
), ),

View file

@ -21,6 +21,7 @@ import {
ControlGroupInput, ControlGroupInput,
ControlGroupOutput, ControlGroupOutput,
ControlGroupReduxState, ControlGroupReduxState,
ControlGroupSettings,
ControlPanelState, ControlPanelState,
ControlsPanels, ControlsPanels,
CONTROL_GROUP_TYPE, CONTROL_GROUP_TYPE,
@ -87,7 +88,9 @@ export class ControlGroupContainer extends Container<
}; };
public getMostRelevantDataViewId = () => { public getMostRelevantDataViewId = () => {
return this.lastUsedDataViewId ?? this.relevantDataViewId; const staticDataViewId =
this.getReduxEmbeddableTools().getState().componentState.staticDataViewId;
return staticDataViewId ?? this.lastUsedDataViewId ?? this.relevantDataViewId;
}; };
public getReduxEmbeddableTools = () => { public getReduxEmbeddableTools = () => {
@ -134,7 +137,8 @@ export class ControlGroupContainer extends Container<
constructor( constructor(
reduxEmbeddablePackage: ReduxEmbeddablePackage, reduxEmbeddablePackage: ReduxEmbeddablePackage,
initialInput: ControlGroupInput, initialInput: ControlGroupInput,
parent?: Container parent?: Container,
settings?: ControlGroupSettings
) { ) {
super( super(
initialInput, initialInput,
@ -155,6 +159,7 @@ export class ControlGroupContainer extends Container<
>({ >({
embeddable: this, embeddable: this,
reducers: controlGroupReducers, reducers: controlGroupReducers,
initialComponentState: settings,
}); });
// when all children are ready setup subscriptions // when all children are ready setup subscriptions

View file

@ -19,7 +19,7 @@ import { Container, EmbeddableFactoryDefinition } from '@kbn/embeddable-plugin/p
import { lazyLoadReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/public'; import { lazyLoadReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/public';
import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common';
import { ControlGroupInput, CONTROL_GROUP_TYPE } from '../types'; import { ControlGroupInput, ControlGroupSettings, CONTROL_GROUP_TYPE } from '../types';
import { import {
createControlGroupExtract, createControlGroupExtract,
createControlGroupInject, createControlGroupInject,
@ -49,9 +49,13 @@ export class ControlGroupContainerFactory implements EmbeddableFactoryDefinition
return getDefaultControlGroupInput(); return getDefaultControlGroupInput();
} }
public create = async (initialInput: ControlGroupInput, parent?: Container) => { public create = async (
initialInput: ControlGroupInput,
parent?: Container,
settings?: ControlGroupSettings
) => {
const reduxEmbeddablePackage = await lazyLoadReduxEmbeddablePackage(); const reduxEmbeddablePackage = await lazyLoadReduxEmbeddablePackage();
const { ControlGroupContainer } = await import('./control_group_container'); const { ControlGroupContainer } = await import('./control_group_container');
return new ControlGroupContainer(reduxEmbeddablePackage, initialInput, parent); return new ControlGroupContainer(reduxEmbeddablePackage, initialInput, parent, settings);
}; };
} }

View file

@ -15,7 +15,26 @@ export type ControlGroupOutput = ContainerOutput &
Omit<CommonControlOutput, 'dataViewId'> & { dataViewIds: string[] }; Omit<CommonControlOutput, 'dataViewId'> & { dataViewIds: string[] };
// public only - redux embeddable state type // public only - redux embeddable state type
export type ControlGroupReduxState = ReduxEmbeddableState<ControlGroupInput, ControlGroupOutput>; export type ControlGroupReduxState = ReduxEmbeddableState<
ControlGroupInput,
ControlGroupOutput,
ControlGroupSettings
>;
export interface ControlGroupCreationOptions {
initialInput?: Partial<ControlGroupInput>;
settings?: ControlGroupSettings;
}
export interface ControlGroupSettings {
showAddButton?: boolean;
staticDataViewId?: string;
editorConfig?: {
hideDataViewSelector?: boolean;
hideWidthSettings?: boolean;
hideAdditionalSettings?: boolean;
};
}
export { export {
type ControlsPanels, type ControlsPanels,

View file

@ -60,18 +60,20 @@ export const ControlsContent: React.FC<Props> = ({
return ( return (
<LazyControlsRenderer <LazyControlsRenderer
filters={filters} filters={filters}
getInitialInput={async () => ({ getCreationOptions={async () => ({
id: dataView.id ?? '', initialInput: {
type: CONTROL_GROUP_TYPE, id: dataView.id ?? '',
timeRange, type: CONTROL_GROUP_TYPE,
refreshConfig: REFRESH_CONFIG, timeRange,
viewMode: ViewMode.VIEW, refreshConfig: REFRESH_CONFIG,
filters: [...filters], viewMode: ViewMode.VIEW,
query, filters: [...filters],
chainingSystem: 'HIERARCHICAL', query,
controlStyle: 'oneLine', chainingSystem: 'HIERARCHICAL',
defaultControlWidth: 'small', controlStyle: 'oneLine',
panels: controlPanel, defaultControlWidth: 'small',
panels: controlPanel,
},
})} })}
onLoadComplete={(newControlGroup) => { onLoadComplete={(newControlGroup) => {
setControlGroup(newControlGroup); setControlGroup(newControlGroup);

View file

@ -42,15 +42,17 @@ export class Timeslider extends Component<Props, {}> {
this._isMounted = true; this._isMounted = true;
} }
_getInitialInput = async ( _getCreationOptions = async (
initialInput: Partial<ControlGroupInput>, initialInput: Partial<ControlGroupInput>,
builder: typeof controlGroupInputBuilder builder: typeof controlGroupInputBuilder
) => { ) => {
builder.addTimeSliderControl(initialInput); builder.addTimeSliderControl(initialInput);
return { return {
...initialInput, initialInput: {
viewMode: ViewMode.VIEW, ...initialInput,
timeRange: this.props.timeRange, viewMode: ViewMode.VIEW,
timeRange: this.props.timeRange,
},
}; };
}; };
@ -91,7 +93,7 @@ export class Timeslider extends Component<Props, {}> {
<div className="mapTimeslider mapTimeslider--animation"> <div className="mapTimeslider mapTimeslider--animation">
<ControlGroupRenderer <ControlGroupRenderer
onLoadComplete={this._onLoadComplete} onLoadComplete={this._onLoadComplete}
getInitialInput={this._getInitialInput} getCreationOptions={this._getCreationOptions}
timeRange={this.props.timeRange} timeRange={this.props.timeRange}
/> />
</div> </div>

View file

@ -277,7 +277,7 @@ const FilterGroupComponent = (props: PropsWithChildren<FilterGroupProps>) => {
}); });
}); });
return initialInput; return { initialInput };
}, },
[dataViewId, timeRange, filters, chainingSystem, query, selectControlsWithPriority] [dataViewId, timeRange, filters, chainingSystem, query, selectControlsWithPriority]
); );
@ -336,7 +336,7 @@ const FilterGroupComponent = (props: PropsWithChildren<FilterGroupProps>) => {
<EuiFlexItem grow={true} data-test-subj="filter_group__items"> <EuiFlexItem grow={true} data-test-subj="filter_group__items">
<ControlGroupRenderer <ControlGroupRenderer
onLoadComplete={onControlGroupLoadHandler} onLoadComplete={onControlGroupLoadHandler}
getInitialInput={setOptions} getCreationOptions={setOptions}
/> />
{!controlGroup ? <FilterGroupLoading /> : null} {!controlGroup ? <FilterGroupLoading /> : null}
</EuiFlexItem> </EuiFlexItem>