mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[control group] implement lastUsedDataViewId$ (#190269)
PR added `lastUsedDataViewId$` to `ControlGroupApi`. `lastUsedDataViewId$` is implemented in `initializeControlsManager`. PR also cleaned up typings by removing `DataControlEditorState`. `DataControlEditorState` was a weird smooshing together of DefaultDataControlState and stuff used by the editor. Instead of `DataControlEditorState`, values used by the editor are just passed in as top level keys. --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
d24105d313
commit
3ad861e4b1
13 changed files with 211 additions and 105 deletions
|
@ -139,7 +139,6 @@ export const ReactControlExample = ({
|
|||
addNewPanel: () => {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
lastUsedDataViewId: new BehaviorSubject<string>(WEB_LOGS_DATA_VIEW_ID),
|
||||
saveNotification$,
|
||||
reload$,
|
||||
};
|
||||
|
|
|
@ -73,7 +73,13 @@ export const getControlGroupEmbeddableFactory = (services: {
|
|||
} = initialRuntimeState;
|
||||
|
||||
const autoApplySelections$ = new BehaviorSubject<boolean>(autoApplySelections);
|
||||
const controlsManager = initControlsManager(initialChildControlState);
|
||||
const parentDataViewId = apiPublishesDataViews(parentApi)
|
||||
? parentApi.dataViews.value?.[0]?.id
|
||||
: undefined;
|
||||
const controlsManager = initControlsManager(
|
||||
initialChildControlState,
|
||||
parentDataViewId ?? (await services.dataViews.getDefaultId())
|
||||
);
|
||||
const selectionsManager = initSelectionsManager({
|
||||
...controlsManager.api,
|
||||
autoApplySelections$,
|
||||
|
@ -172,6 +178,7 @@ export const getControlGroupEmbeddableFactory = (services: {
|
|||
initialState: {
|
||||
grow: api.grow.getValue(),
|
||||
width: api.width.getValue(),
|
||||
dataViewId: controlsManager.api.lastUsedDataViewId$.value,
|
||||
},
|
||||
onSave: ({ type: controlType, state: initialState }) => {
|
||||
api.addNewPanel({
|
||||
|
|
|
@ -7,19 +7,24 @@
|
|||
*/
|
||||
|
||||
import { DefaultControlApi } from '../controls/types';
|
||||
import { initControlsManager } from './init_controls_manager';
|
||||
import { initControlsManager, getLastUsedDataViewId } from './init_controls_manager';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn().mockReturnValue('delta'),
|
||||
}));
|
||||
|
||||
const DEFAULT_DATA_VIEW_ID = 'myDataView';
|
||||
|
||||
describe('PresentationContainer api', () => {
|
||||
test('addNewPanel should add control at end of controls', async () => {
|
||||
const controlsManager = initControlsManager({
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
charlie: { type: 'testControl', order: 2 },
|
||||
});
|
||||
const controlsManager = initControlsManager(
|
||||
{
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
charlie: { type: 'testControl', order: 2 },
|
||||
},
|
||||
DEFAULT_DATA_VIEW_ID
|
||||
);
|
||||
const addNewPanelPromise = controlsManager.api.addNewPanel({
|
||||
panelType: 'testControl',
|
||||
initialState: {},
|
||||
|
@ -35,11 +40,14 @@ describe('PresentationContainer api', () => {
|
|||
});
|
||||
|
||||
test('removePanel should remove control', () => {
|
||||
const controlsManager = initControlsManager({
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
charlie: { type: 'testControl', order: 2 },
|
||||
});
|
||||
const controlsManager = initControlsManager(
|
||||
{
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
charlie: { type: 'testControl', order: 2 },
|
||||
},
|
||||
DEFAULT_DATA_VIEW_ID
|
||||
);
|
||||
controlsManager.api.removePanel('bravo');
|
||||
expect(controlsManager.controlsInOrder$.value.map((element) => element.id)).toEqual([
|
||||
'alpha',
|
||||
|
@ -48,11 +56,14 @@ describe('PresentationContainer api', () => {
|
|||
});
|
||||
|
||||
test('replacePanel should replace control', async () => {
|
||||
const controlsManager = initControlsManager({
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
charlie: { type: 'testControl', order: 2 },
|
||||
});
|
||||
const controlsManager = initControlsManager(
|
||||
{
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
charlie: { type: 'testControl', order: 2 },
|
||||
},
|
||||
DEFAULT_DATA_VIEW_ID
|
||||
);
|
||||
const replacePanelPromise = controlsManager.api.replacePanel('bravo', {
|
||||
panelType: 'testControl',
|
||||
initialState: {},
|
||||
|
@ -68,10 +79,13 @@ describe('PresentationContainer api', () => {
|
|||
|
||||
describe('untilInitialized', () => {
|
||||
test('should not resolve until all controls are initialized', async () => {
|
||||
const controlsManager = initControlsManager({
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
});
|
||||
const controlsManager = initControlsManager(
|
||||
{
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
},
|
||||
DEFAULT_DATA_VIEW_ID
|
||||
);
|
||||
let isDone = false;
|
||||
controlsManager.api.untilInitialized().then(() => {
|
||||
isDone = true;
|
||||
|
@ -89,10 +103,13 @@ describe('PresentationContainer api', () => {
|
|||
});
|
||||
|
||||
test('should resolve when all control already initialized ', async () => {
|
||||
const controlsManager = initControlsManager({
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
});
|
||||
const controlsManager = initControlsManager(
|
||||
{
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
bravo: { type: 'testControl', order: 1 },
|
||||
},
|
||||
DEFAULT_DATA_VIEW_ID
|
||||
);
|
||||
controlsManager.setControlApi('alpha', {} as unknown as DefaultControlApi);
|
||||
controlsManager.setControlApi('bravo', {} as unknown as DefaultControlApi);
|
||||
|
||||
|
@ -109,10 +126,13 @@ describe('PresentationContainer api', () => {
|
|||
|
||||
describe('snapshotControlsRuntimeState', () => {
|
||||
test('should snapshot runtime state for all controls', async () => {
|
||||
const controlsManager = initControlsManager({
|
||||
alpha: { type: 'testControl', order: 1 },
|
||||
bravo: { type: 'testControl', order: 0 },
|
||||
});
|
||||
const controlsManager = initControlsManager(
|
||||
{
|
||||
alpha: { type: 'testControl', order: 1 },
|
||||
bravo: { type: 'testControl', order: 0 },
|
||||
},
|
||||
DEFAULT_DATA_VIEW_ID
|
||||
);
|
||||
controlsManager.setControlApi('alpha', {
|
||||
snapshotRuntimeState: () => {
|
||||
return { key1: 'alpha value' };
|
||||
|
@ -137,3 +157,33 @@ describe('snapshotControlsRuntimeState', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastUsedDataViewId', () => {
|
||||
test('should return last used data view id', () => {
|
||||
const dataViewId = getLastUsedDataViewId(
|
||||
[
|
||||
{ id: 'alpha', type: 'testControl' },
|
||||
{ id: 'bravo', type: 'testControl' },
|
||||
{ id: 'charlie', type: 'testControl' },
|
||||
],
|
||||
{
|
||||
alpha: { dataViewId: '1', type: 'testControl', order: 0 },
|
||||
bravo: { dataViewId: '2', type: 'testControl', order: 1 },
|
||||
charlie: { type: 'testControl', order: 2 },
|
||||
}
|
||||
);
|
||||
expect(dataViewId).toBe('2');
|
||||
});
|
||||
|
||||
test('should return undefined when there are no controls', () => {
|
||||
const dataViewId = getLastUsedDataViewId([], {});
|
||||
expect(dataViewId).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return undefined when there are no controls with data views', () => {
|
||||
const dataViewId = getLastUsedDataViewId([{ id: 'alpha', type: 'testControl' }], {
|
||||
alpha: { type: 'testControl', order: 0 },
|
||||
});
|
||||
expect(dataViewId).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,9 +18,10 @@ import { BehaviorSubject, first, merge } from 'rxjs';
|
|||
import { PublishingSubject, StateComparators } from '@kbn/presentation-publishing';
|
||||
import { omit } from 'lodash';
|
||||
import { apiHasSnapshottableState } from '@kbn/presentation-containers/interfaces/serialized_state';
|
||||
import { ControlPanelsState, ControlPanelState } from './types';
|
||||
import { ControlGroupApi, ControlPanelsState, ControlPanelState } from './types';
|
||||
import { DefaultControlApi, DefaultControlState } from '../controls/types';
|
||||
import { ControlGroupComparatorState } from './control_group_unsaved_changes_api';
|
||||
import { DefaultDataControlState } from '../controls/data_controls/types';
|
||||
|
||||
export type ControlsInOrder = Array<{ id: string; type: string }>;
|
||||
|
||||
|
@ -35,7 +36,10 @@ export function getControlsInOrder(initialControlPanelsState: ControlPanelsState
|
|||
.map(({ id, type }) => ({ id, type })); // filter out `order`
|
||||
}
|
||||
|
||||
export function initControlsManager(initialControlPanelsState: ControlPanelsState) {
|
||||
export function initControlsManager(
|
||||
initialControlPanelsState: ControlPanelsState,
|
||||
defaultDataViewId: string | null
|
||||
) {
|
||||
const lastSavedControlsPanelState$ = new BehaviorSubject(initialControlPanelsState);
|
||||
const initialControlIds = Object.keys(initialControlPanelsState);
|
||||
const children$ = new BehaviorSubject<{ [key: string]: DefaultControlApi }>({});
|
||||
|
@ -45,6 +49,11 @@ export function initControlsManager(initialControlPanelsState: ControlPanelsStat
|
|||
const controlsInOrder$ = new BehaviorSubject<ControlsInOrder>(
|
||||
getControlsInOrder(initialControlPanelsState)
|
||||
);
|
||||
const lastUsedDataViewId$ = new BehaviorSubject<string | undefined>(
|
||||
getLastUsedDataViewId(controlsInOrder$.value, initialControlPanelsState) ??
|
||||
defaultDataViewId ??
|
||||
undefined
|
||||
);
|
||||
|
||||
function untilControlLoaded(
|
||||
id: string
|
||||
|
@ -79,6 +88,9 @@ export function initControlsManager(initialControlPanelsState: ControlPanelsStat
|
|||
{ panelType, initialState }: PanelPackage<DefaultControlState>,
|
||||
index: number
|
||||
) {
|
||||
if ((initialState as DefaultDataControlState)?.dataViewId) {
|
||||
lastUsedDataViewId$.next((initialState as DefaultDataControlState).dataViewId);
|
||||
}
|
||||
const id = generateId();
|
||||
const nextControlsInOrder = [...controlsInOrder$.value];
|
||||
nextControlsInOrder.splice(index, 0, {
|
||||
|
@ -156,6 +168,7 @@ export function initControlsManager(initialControlPanelsState: ControlPanelsStat
|
|||
return controlsRuntimeState;
|
||||
},
|
||||
api: {
|
||||
lastUsedDataViewId$: lastUsedDataViewId$ as PublishingSubject<string | undefined>,
|
||||
getSerializedStateForChild: (childId: string) => {
|
||||
const controlPanelState = controlsPanelState[childId];
|
||||
return controlPanelState ? { rawState: controlPanelState } : undefined;
|
||||
|
@ -196,7 +209,8 @@ export function initControlsManager(initialControlPanelsState: ControlPanelsStat
|
|||
});
|
||||
},
|
||||
} as PresentationContainer &
|
||||
HasSerializedChildState<ControlPanelState> & { untilInitialized: () => Promise<void> },
|
||||
HasSerializedChildState<ControlPanelState> &
|
||||
Pick<ControlGroupApi, 'untilInitialized' | 'lastUsedDataViewId$'>,
|
||||
comparators: {
|
||||
controlsInOrder: [
|
||||
controlsInOrder$,
|
||||
|
@ -222,3 +236,21 @@ export function initControlsManager(initialControlPanelsState: ControlPanelsStat
|
|||
>,
|
||||
};
|
||||
}
|
||||
|
||||
export function getLastUsedDataViewId(
|
||||
controlsInOrder: ControlsInOrder,
|
||||
initialControlPanelsState: ControlPanelsState<
|
||||
ControlPanelState & Partial<DefaultDataControlState>
|
||||
>
|
||||
) {
|
||||
let dataViewId: string | undefined;
|
||||
for (let i = controlsInOrder.length - 1; i >= 0; i--) {
|
||||
const controlId = controlsInOrder[i].id;
|
||||
const controlState = initialControlPanelsState[controlId];
|
||||
if (controlState?.dataViewId) {
|
||||
dataViewId = controlState.dataViewId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return dataViewId;
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ export type ControlGroupApi = PresentationContainer &
|
|||
openAddDataControlFlyout: (settings?: {
|
||||
controlInputTransform?: ControlInputTransform;
|
||||
}) => void;
|
||||
lastUsedDataViewId$: PublishingSubject<string | undefined>;
|
||||
};
|
||||
|
||||
export interface ControlGroupRuntimeState {
|
||||
|
|
|
@ -25,7 +25,6 @@ jest.mock('../../control_factory_registry', () => ({
|
|||
import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../../common';
|
||||
import { ControlGroupApi } from '../../control_group/types';
|
||||
import { DataControlEditor } from './data_control_editor';
|
||||
import { DataControlEditorState } from './open_data_control_editor';
|
||||
import {
|
||||
getMockedOptionsListControlFactory,
|
||||
getMockedRangeSliderControlFactory,
|
||||
|
@ -57,7 +56,6 @@ mockDataViews.get = jest.fn().mockResolvedValue(mockDataView);
|
|||
|
||||
const dashboardApi = {
|
||||
timeRange$: new BehaviorSubject<TimeRange | undefined>(undefined),
|
||||
lastUsedDataViewId$: new BehaviorSubject<string>(mockDataView.id!),
|
||||
};
|
||||
const controlGroupApi = {
|
||||
parentApi: dashboardApi,
|
||||
|
@ -68,8 +66,14 @@ const controlGroupApi = {
|
|||
describe('Data control editor', () => {
|
||||
const mountComponent = async ({
|
||||
initialState,
|
||||
controlId,
|
||||
controlType,
|
||||
initialDefaultPanelTitle,
|
||||
}: {
|
||||
initialState?: Partial<DataControlEditorState>;
|
||||
initialState?: Partial<DefaultDataControlState>;
|
||||
controlId?: string;
|
||||
controlType?: string;
|
||||
initialDefaultPanelTitle?: string;
|
||||
}) => {
|
||||
mockDataViews.get = jest.fn().mockResolvedValue(mockDataView);
|
||||
|
||||
|
@ -78,11 +82,14 @@ describe('Data control editor', () => {
|
|||
<DataControlEditor
|
||||
onCancel={() => {}}
|
||||
onSave={() => {}}
|
||||
parentApi={controlGroupApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
initialState={{
|
||||
dataViewId: dashboardApi.lastUsedDataViewId$.getValue(),
|
||||
dataViewId: mockDataView.id,
|
||||
...initialState,
|
||||
}}
|
||||
controlId={controlId}
|
||||
controlType={controlType}
|
||||
initialDefaultPanelTitle={initialDefaultPanelTitle}
|
||||
services={{ dataViews: mockDataViews }}
|
||||
/>
|
||||
</I18nProvider>
|
||||
|
@ -238,11 +245,11 @@ describe('Data control editor', () => {
|
|||
test('auto-fills input with the default title', async () => {
|
||||
const controlEditor = await mountComponent({
|
||||
initialState: {
|
||||
controlType: 'optionsList',
|
||||
controlId: 'testId',
|
||||
fieldName: 'machine.os.raw',
|
||||
defaultPanelTitle: 'OS',
|
||||
},
|
||||
controlType: 'optionsList',
|
||||
controlId: 'testId',
|
||||
initialDefaultPanelTitle: 'OS',
|
||||
});
|
||||
const titleInput = await controlEditor.findByTestId('control-editor-title-input');
|
||||
expect(titleInput.getAttribute('value')).toBe('OS');
|
||||
|
@ -252,11 +259,11 @@ describe('Data control editor', () => {
|
|||
test('auto-fills input with the custom title', async () => {
|
||||
const controlEditor = await mountComponent({
|
||||
initialState: {
|
||||
controlType: 'optionsList',
|
||||
controlId: 'testId',
|
||||
fieldName: 'machine.os.raw',
|
||||
title: 'Custom title',
|
||||
},
|
||||
controlType: 'optionsList',
|
||||
controlId: 'testId',
|
||||
});
|
||||
const titleInput = await controlEditor.findByTestId('control-editor-title-input');
|
||||
expect(titleInput.getAttribute('value')).toBe('Custom title');
|
||||
|
@ -267,10 +274,10 @@ describe('Data control editor', () => {
|
|||
test('selects the provided control type', async () => {
|
||||
const controlEditor = await mountComponent({
|
||||
initialState: {
|
||||
controlType: 'rangeSlider',
|
||||
controlId: 'testId',
|
||||
fieldName: 'bytes',
|
||||
},
|
||||
controlType: 'rangeSlider',
|
||||
controlId: 'testId',
|
||||
});
|
||||
|
||||
expect(controlEditor.getByTestId('create__optionsList')).toBeEnabled();
|
||||
|
|
|
@ -46,14 +46,18 @@ import { getAllControlTypes, getControlFactory } from '../../control_factory_reg
|
|||
import { ControlGroupApi } from '../../control_group/types';
|
||||
import { DataControlEditorStrings } from './data_control_constants';
|
||||
import { getDataControlFieldRegistry } from './data_control_editor_utils';
|
||||
import { DataControlEditorState } from './open_data_control_editor';
|
||||
import { DataControlFactory, DefaultDataControlState, isDataControlFactory } from './types';
|
||||
|
||||
export interface ControlEditorProps<State extends DataControlEditorState = DataControlEditorState> {
|
||||
initialState: State;
|
||||
parentApi: ControlGroupApi; // controls must always have a parent API
|
||||
onCancel: (newState: State) => void;
|
||||
onSave: (newState: State, type: string) => void;
|
||||
export interface ControlEditorProps<
|
||||
State extends DefaultDataControlState = DefaultDataControlState
|
||||
> {
|
||||
initialState: Partial<State>;
|
||||
controlType?: string;
|
||||
controlId?: string;
|
||||
initialDefaultPanelTitle?: string;
|
||||
controlGroupApi: ControlGroupApi; // controls must always have a parent API
|
||||
onCancel: (newState: Partial<State>) => void;
|
||||
onSave: (newState: Partial<State>, type: string) => void;
|
||||
services: {
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
};
|
||||
|
@ -130,27 +134,27 @@ const CompatibleControlTypesComponent = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const DataControlEditor = <State extends DataControlEditorState = DataControlEditorState>({
|
||||
export const DataControlEditor = <State extends DefaultDataControlState = DefaultDataControlState>({
|
||||
initialState,
|
||||
controlId,
|
||||
controlType,
|
||||
initialDefaultPanelTitle,
|
||||
onSave,
|
||||
onCancel,
|
||||
parentApi: controlGroup,
|
||||
controlGroupApi,
|
||||
/** TODO: These should not be props */
|
||||
services: { dataViews: dataViewService },
|
||||
}: ControlEditorProps<State>) => {
|
||||
const [defaultGrow, defaultWidth] = useBatchedPublishingSubjects(
|
||||
controlGroup.grow,
|
||||
controlGroup.width
|
||||
// controlGroup.parentApi?.lastUsedDataViewId, // TODO: Make this work
|
||||
controlGroupApi.grow,
|
||||
controlGroupApi.width
|
||||
);
|
||||
const [editorState, setEditorState] = useState<State>(initialState);
|
||||
const [editorState, setEditorState] = useState<Partial<State>>(initialState);
|
||||
const [defaultPanelTitle, setDefaultPanelTitle] = useState<string>(
|
||||
initialState.defaultPanelTitle ?? initialState.fieldName ?? ''
|
||||
initialDefaultPanelTitle ?? initialState.fieldName ?? ''
|
||||
);
|
||||
const [panelTitle, setPanelTitle] = useState<string>(initialState.title ?? defaultPanelTitle);
|
||||
const [selectedControlType, setSelectedControlType] = useState<string | undefined>(
|
||||
initialState.controlType
|
||||
);
|
||||
const [selectedControlType, setSelectedControlType] = useState<string | undefined>(controlType);
|
||||
const [controlOptionsValid, setControlOptionsValid] = useState<boolean>(true);
|
||||
|
||||
/** TODO: Make `editorConfig` work when refactoring the `ControlGroupRenderer` */
|
||||
|
@ -209,22 +213,22 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon
|
|||
data-test-subj="control-editor-custom-settings"
|
||||
>
|
||||
<CustomSettings
|
||||
initialState={initialState as DefaultDataControlState}
|
||||
initialState={initialState}
|
||||
field={fieldRegistry[editorState.fieldName].field}
|
||||
updateState={(newState) => setEditorState({ ...editorState, ...newState })}
|
||||
setControlEditorValid={setControlOptionsValid}
|
||||
parentApi={controlGroup}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
}, [fieldRegistry, selectedControlType, initialState, editorState, controlGroup]);
|
||||
}, [fieldRegistry, selectedControlType, initialState, editorState, controlGroupApi]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
{!initialState.controlId // if no ID, then we are creating a new control
|
||||
{!controlId // if no ID, then we are creating a new control
|
||||
? DataControlEditorStrings.manageControl.getFlyoutCreateTitle()
|
||||
: DataControlEditorStrings.manageControl.getFlyoutEditTitle()}
|
||||
</h2>
|
||||
|
@ -385,7 +389,7 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon
|
|||
{/* )} */}
|
||||
</EuiDescribedFormGroup>
|
||||
{CustomSettingsComponent}
|
||||
{initialState.controlId && (
|
||||
{controlId && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiButtonEmpty
|
||||
|
@ -395,7 +399,7 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon
|
|||
color="danger"
|
||||
onClick={() => {
|
||||
onCancel(initialState); // don't want to show "lost changes" warning
|
||||
controlGroup.removePanel(initialState.controlId!);
|
||||
controlGroupApi.removePanel(controlId!);
|
||||
}}
|
||||
>
|
||||
{DataControlEditorStrings.manageControl.getDeleteButtonTitle()}
|
||||
|
|
|
@ -36,7 +36,7 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
* responsible for managing
|
||||
*/
|
||||
editorStateManager: ControlStateManager<EditorState>,
|
||||
controlGroup: ControlGroupApi,
|
||||
controlGroupApi: ControlGroupApi,
|
||||
services: {
|
||||
core: CoreStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
|
@ -159,22 +159,24 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
(Object.keys(initialState) as Array<keyof DefaultDataControlState & EditorState>).forEach(
|
||||
(key) => {
|
||||
if (!isEqual(mergedStateManager[key].getValue(), newState[key])) {
|
||||
mergedStateManager[key].next(newState[key]);
|
||||
mergedStateManager[key].next(
|
||||
newState[key] as DefaultDataControlState & EditorState[typeof key]
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// replace the control with a new one of the updated type
|
||||
controlGroup.replacePanel(controlId, { panelType: newType, initialState: newState });
|
||||
controlGroupApi.replacePanel(controlId, { panelType: newType, initialState: newState });
|
||||
}
|
||||
},
|
||||
initialState: {
|
||||
...initialState,
|
||||
controlType,
|
||||
controlId,
|
||||
defaultPanelTitle: defaultPanelTitle.getValue(),
|
||||
},
|
||||
controlGroupApi: controlGroup,
|
||||
controlType,
|
||||
controlId,
|
||||
initialDefaultPanelTitle: defaultPanelTitle.getValue(),
|
||||
controlGroupApi,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -20,23 +20,22 @@ import { ControlGroupApi } from '../../control_group/types';
|
|||
import { DataControlEditor } from './data_control_editor';
|
||||
import { DefaultDataControlState } from './types';
|
||||
|
||||
export type DataControlEditorState = Partial<DefaultDataControlState> & {
|
||||
fieldName?: string;
|
||||
controlType?: string;
|
||||
controlId?: string;
|
||||
defaultPanelTitle?: string;
|
||||
};
|
||||
|
||||
export const openDataControlEditor = <
|
||||
State extends DataControlEditorState = DataControlEditorState
|
||||
State extends DefaultDataControlState = DefaultDataControlState
|
||||
>({
|
||||
initialState,
|
||||
controlType,
|
||||
controlId,
|
||||
initialDefaultPanelTitle,
|
||||
onSave,
|
||||
controlGroupApi,
|
||||
services,
|
||||
}: {
|
||||
initialState: State;
|
||||
onSave: ({ type, state }: { type: string; state: State }) => void;
|
||||
initialState: Partial<State>;
|
||||
controlType?: string;
|
||||
controlId?: string;
|
||||
initialDefaultPanelTitle?: string;
|
||||
onSave: ({ type, state }: { type: string; state: Partial<State> }) => void;
|
||||
controlGroupApi: ControlGroupApi;
|
||||
services: {
|
||||
core: CoreStart;
|
||||
|
@ -50,7 +49,7 @@ export const openDataControlEditor = <
|
|||
overlayRef.close();
|
||||
};
|
||||
|
||||
const onCancel = (newState: State, overlay: OverlayRef) => {
|
||||
const onCancel = (newState: Partial<State>, overlay: OverlayRef) => {
|
||||
if (deepEqual(initialState, newState)) {
|
||||
closeOverlay(overlay);
|
||||
return;
|
||||
|
@ -83,8 +82,11 @@ export const openDataControlEditor = <
|
|||
const overlay = services.core.overlays.openFlyout(
|
||||
toMountPoint(
|
||||
<DataControlEditor<State>
|
||||
parentApi={controlGroupApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
initialState={initialState}
|
||||
controlType={controlType}
|
||||
controlId={controlId}
|
||||
initialDefaultPanelTitle={initialDefaultPanelTitle}
|
||||
onCancel={(state) => {
|
||||
onCancel(state, overlay);
|
||||
}}
|
||||
|
|
|
@ -34,9 +34,9 @@ describe('Options list sorting button', () => {
|
|||
const mountComponent = ({
|
||||
initialState,
|
||||
field,
|
||||
parentApi = getMockedControlGroupApi(),
|
||||
controlGroupApi = getMockedControlGroupApi(),
|
||||
}: Pick<CustomOptionsComponentProps, 'initialState' | 'field'> & {
|
||||
parentApi?: ControlGroupApi;
|
||||
controlGroupApi?: ControlGroupApi;
|
||||
}) => {
|
||||
const component = render(
|
||||
<OptionsListEditorOptions
|
||||
|
@ -44,7 +44,7 @@ describe('Options list sorting button', () => {
|
|||
field={field}
|
||||
updateState={updateState}
|
||||
setControlEditorValid={jest.fn()}
|
||||
parentApi={parentApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
return component;
|
||||
|
@ -85,7 +85,7 @@ describe('Options list sorting button', () => {
|
|||
const component = mountComponent({
|
||||
initialState: getMockedState(),
|
||||
field: { type: 'string' } as DataViewField,
|
||||
parentApi: controlGroupApi,
|
||||
controlGroupApi,
|
||||
});
|
||||
expect(
|
||||
component.queryByTestId('optionsListControl__searchOptionsRadioGroup')
|
||||
|
@ -152,14 +152,14 @@ describe('Options list sorting button', () => {
|
|||
describe('responds to field type changing', () => {
|
||||
test('reset back to initial state when valid', async () => {
|
||||
const initialState = getMockedState({ searchTechnique: 'exact' });
|
||||
const parentApi = getMockedControlGroupApi();
|
||||
const controlGroupApi = getMockedControlGroupApi();
|
||||
const component = render(
|
||||
<OptionsListEditorOptions
|
||||
initialState={initialState}
|
||||
field={{ type: 'string' } as DataViewField}
|
||||
updateState={updateState}
|
||||
setControlEditorValid={jest.fn()}
|
||||
parentApi={parentApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -175,7 +175,7 @@ describe('Options list sorting button', () => {
|
|||
field={{ type: 'ip' } as DataViewField} // initial search technique IS valid
|
||||
updateState={jest.fn()}
|
||||
setControlEditorValid={jest.fn()}
|
||||
parentApi={parentApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -187,14 +187,14 @@ describe('Options list sorting button', () => {
|
|||
|
||||
test('if the current selection is valid, send that to the parent editor state', async () => {
|
||||
const initialState = getMockedState();
|
||||
const parentApi = getMockedControlGroupApi();
|
||||
const controlGroupApi = getMockedControlGroupApi();
|
||||
const component = render(
|
||||
<OptionsListEditorOptions
|
||||
initialState={initialState}
|
||||
field={{ type: 'string' } as DataViewField}
|
||||
updateState={updateState}
|
||||
setControlEditorValid={jest.fn()}
|
||||
parentApi={parentApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -218,7 +218,7 @@ describe('Options list sorting button', () => {
|
|||
field={{ type: 'number' } as DataViewField} // current selected search technique IS valid, initial state is not
|
||||
updateState={jest.fn()}
|
||||
setControlEditorValid={jest.fn()}
|
||||
parentApi={parentApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -227,14 +227,14 @@ describe('Options list sorting button', () => {
|
|||
|
||||
test('if neither the initial or current search technique is valid, revert to the default', async () => {
|
||||
const initialState = getMockedState({ searchTechnique: 'wildcard' });
|
||||
const parentApi = getMockedControlGroupApi();
|
||||
const controlGroupApi = getMockedControlGroupApi();
|
||||
const component = render(
|
||||
<OptionsListEditorOptions
|
||||
initialState={initialState}
|
||||
field={{ type: 'string' } as DataViewField}
|
||||
updateState={updateState}
|
||||
setControlEditorValid={jest.fn()}
|
||||
parentApi={parentApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -250,7 +250,7 @@ describe('Options list sorting button', () => {
|
|||
field={{ type: 'number' } as DataViewField} // neither initial nor current search technique is valid
|
||||
updateState={jest.fn()}
|
||||
setControlEditorValid={jest.fn()}
|
||||
parentApi={parentApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -71,9 +71,11 @@ export const OptionsListEditorOptions = ({
|
|||
initialState,
|
||||
field,
|
||||
updateState,
|
||||
parentApi,
|
||||
controlGroupApi,
|
||||
}: CustomOptionsComponentProps<OptionsListControlState>) => {
|
||||
const allowExpensiveQueries = useStateFromPublishingSubject(parentApi.allowExpensiveQueries$);
|
||||
const allowExpensiveQueries = useStateFromPublishingSubject(
|
||||
controlGroupApi.allowExpensiveQueries$
|
||||
);
|
||||
|
||||
const [singleSelect, setSingleSelect] = useState<boolean>(initialState.singleSelect ?? false);
|
||||
const [runPastTimeout, setRunPastTimeout] = useState<boolean>(
|
||||
|
|
|
@ -243,7 +243,7 @@ describe('RangesliderControlApi', () => {
|
|||
field={{} as DataViewField}
|
||||
updateState={jest.fn()}
|
||||
setControlEditorValid={jest.fn()}
|
||||
parentApi={controlGroupApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
|
@ -260,7 +260,7 @@ describe('RangesliderControlApi', () => {
|
|||
field={{} as DataViewField}
|
||||
updateState={jest.fn()}
|
||||
setControlEditorValid={setControlEditorValid}
|
||||
parentApi={controlGroupApi}
|
||||
controlGroupApi={controlGroupApi}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -38,11 +38,11 @@ export type DataControlApi = DefaultControlApi &
|
|||
export interface CustomOptionsComponentProps<
|
||||
State extends DefaultDataControlState = DefaultDataControlState
|
||||
> {
|
||||
initialState: Omit<State, 'fieldName'>;
|
||||
initialState: Partial<State>;
|
||||
field: DataViewField;
|
||||
updateState: (newState: Partial<State>) => void;
|
||||
setControlEditorValid: (valid: boolean) => void;
|
||||
parentApi: ControlGroupApi;
|
||||
controlGroupApi: ControlGroupApi;
|
||||
}
|
||||
|
||||
export interface DataControlFactory<
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue