remove redux tools from presentation_utils plugin (#197891)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2024-12-10 15:08:01 -07:00 committed by GitHub
parent 5b6887dd3d
commit fb885eaeff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 59 additions and 694 deletions

View file

@ -39,8 +39,8 @@ import {
connectToQueryState,
syncGlobalQueryStateWithUrl,
} from '@kbn/data-plugin/public';
import { cleanFiltersForSerialize } from '@kbn/presentation-util-plugin/public';
import moment, { Moment } from 'moment';
import { cleanFiltersForSerialize } from '../utils/clean_filters_for_serialize';
import { dataService } from '../services/kibana_services';
import { DashboardCreationOptions, DashboardState } from './types';
import { DEFAULT_DASHBOARD_INPUT, GLOBAL_STATE_STORAGE_KEY } from '../dashboard_constants';

View file

@ -8,7 +8,6 @@
*/
import type { ContainerOutput } from '@kbn/embeddable-plugin/public';
import type { ReduxEmbeddableState } from '@kbn/presentation-util-plugin/public';
import { SerializableRecord } from '@kbn/utility-types';
import { ControlGroupRuntimeState } from '@kbn/controls-plugin/public';
@ -19,11 +18,6 @@ export interface UnsavedPanelState {
[key: string]: object | undefined;
}
export type DashboardReduxState = ReduxEmbeddableState<
DashboardContainerInput,
DashboardContainerOutput
>;
export type DashboardRedirect = (props: RedirectToProps) => void;
export type RedirectToProps =
| { destination: 'dashboard'; id?: string; useReplace?: boolean; editMode?: boolean }

View file

@ -14,8 +14,8 @@ import { injectSearchSourceReferences } from '@kbn/data-plugin/public';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { Filter, Query } from '@kbn/es-query';
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public';
import { cleanFiltersForSerialize } from '@kbn/presentation-util-plugin/public';
import { cleanFiltersForSerialize } from '../../../utils/clean_filters_for_serialize';
import { getDashboardContentManagementCache } from '..';
import { convertPanelsArrayToPanelMap, injectReferences } from '../../../../common';
import type { DashboardGetIn, DashboardGetOut } from '../../../../server/content_management';

View file

@ -0,0 +1,38 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Filter } from '@kbn/es-query';
import { cleanFiltersForSerialize } from './clean_filters_for_serialize';
describe('cleanFiltersForSerialize', () => {
test('should return an empty array if filters is not provided', () => {
expect(cleanFiltersForSerialize()).toEqual([]);
});
test('should remove "meta.value" property from each filter', () => {
const filters: Filter[] = [
{ query: { a: 'a' }, meta: { value: 'value1' } },
{ query: { b: 'b' }, meta: { value: 'value2' } },
];
const cleanedFilters = cleanFiltersForSerialize(filters);
expect(cleanedFilters[0]).toEqual({ query: { a: 'a' }, meta: {} });
expect(cleanedFilters[1]).toEqual({ query: { b: 'b' }, meta: {} });
});
test('should not fail if meta is missing from filters', () => {
const filters: Filter[] = [{ query: { a: 'a' } }, { query: { b: 'b' } }] as unknown as Filter[];
const cleanedFilters = cleanFiltersForSerialize(filters as unknown as Filter[]);
expect(cleanedFilters[0]).toEqual({ query: { a: 'a' } });
expect(cleanedFilters[1]).toEqual({ query: { b: 'b' } });
});
});

View file

@ -0,0 +1,18 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Filter } from '@kbn/es-query';
export function cleanFiltersForSerialize(filters?: Filter[]): Filter[] {
if (!filters) return [];
return filters.map((filter) => {
if (filter.meta?.value) delete filter.meta.value;
return filter;
});
}

View file

@ -8,51 +8,4 @@ tags: ['kibana', 'presentation', 'services']
related: []
---
## Introduction
The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas).
## 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, and embeddable input sync. To use this system, a developer can use CreateReduxEmbeddableTools in the constructor of their embeddable, supplying a collection 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>
### Accessing Actions and State
From components under the embeddable, actions, containerActions, and the current state of the redux store are accessed via the embeddable instance. You can pass the embeddable instance down as a prop, or use a context.
<DocAccordion buttonContent="Accessing Redux Embeddable Context" initialIsOpen>
```ts
// my_embeddable_component.tsx
const MyEmbeddableComponent = ({ embeddableInstance }: { embeddableInstance: IEmbeddable }) => {
// current state
const specialBoolean = embeddableInstance.select((state) => state.specialBoolean);
// change specialBoolean after 5 seconds
setTimeout(() => embeddableInstance.dispatch.setSpecialBoolean(false), 5000);
}
```
</DocAccordion>
```

View file

@ -30,15 +30,6 @@ export {
DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
} from './components';
export {
lazyLoadReduxToolsPackage,
cleanFiltersForSerialize,
type ReduxEmbeddableState,
type ReduxEmbeddableTools,
type ReduxTools,
type ReduxToolsPackage,
} from './redux_tools';
export type {
ExpressionInputEditorRef,
ExpressionInputProps,

View file

@ -8,9 +8,7 @@
*/
import { PresentationUtilPluginStart } from './types';
import { ReduxToolsPackage, registerExpressionsLanguage } from '.';
import { createReduxEmbeddableTools } from './redux_tools/redux_embeddables/create_redux_embeddable_tools';
import { createReduxTools } from './redux_tools/create_redux_tools';
import { registerExpressionsLanguage } from '.';
import { setStubKibanaServices } from './services/mocks';
const createStartContract = (): PresentationUtilPluginStart => {
@ -31,14 +29,6 @@ export const presentationUtilPluginMock = {
createStartContract,
};
/**
* A non async-imported version of the real redux embeddable tools package for mocking purposes.
*/
export const mockedReduxEmbeddablePackage: ReduxToolsPackage = {
createReduxEmbeddableTools,
createReduxTools,
};
export * from './__stories__/fixtures/flights';
export const setMockedPresentationUtilServices = () => {
setStubKibanaServices();

View file

@ -1,84 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
AnyAction,
Middleware,
createSlice,
configureStore,
SliceCaseReducers,
CaseReducerActions,
} from '@reduxjs/toolkit';
import { v4 as uuidv4 } from 'uuid';
import { createContext } from 'react';
import { createSelectorHook } from 'react-redux';
import { ReduxTools, ReduxToolsReducers, ReduxToolsSetters } from './types';
export const createReduxTools = <
ReduxStateType extends unknown,
ReducerType extends ReduxToolsReducers<ReduxStateType> = ReduxToolsReducers<ReduxStateType>
>({
reducers,
additionalMiddleware,
initialState,
}: {
additionalMiddleware?: Array<Middleware<AnyAction>>;
initialState: ReduxStateType;
reducers: ReducerType;
}): ReduxTools<ReduxStateType, ReducerType> => {
const id = uuidv4();
/**
* Create slice out of reducers and embeddable initial state.
*/
const slice = createSlice<ReduxStateType, SliceCaseReducers<ReduxStateType>>({
initialState,
name: id,
reducers,
});
const store = configureStore({
reducer: slice.reducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(...(additionalMiddleware ?? [])),
});
/**
* Create an object of setter functions by looping through the reducers, and creating a method that dispatches the related
* action to the appropriate store.
*/
const dispatch: ReduxToolsSetters<ReduxStateType, ReducerType> = Object.keys(reducers).reduce(
(acc, key: keyof ReducerType) => {
const sliceAction =
slice.actions[key as keyof CaseReducerActions<SliceCaseReducers<ReduxStateType>, string>];
acc[key] = (payload) => store.dispatch(sliceAction(payload));
return acc;
},
{} as ReduxToolsSetters<ReduxStateType, ReducerType>
);
/**
* Create a selector which can be used by react components to get the latest state values and to re-render when state changes.
*/
const select = createSelectorHook(
createContext({
store,
storeState: store.getState(),
})
);
return {
store,
select,
dispatch,
getState: store.getState,
onStateChange: store.subscribe,
};
};

View file

@ -1,25 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { ReduxToolsPackage } from './types';
export type { ReduxEmbeddableState, ReduxEmbeddableTools } from './redux_embeddables/types';
export { cleanFiltersForSerialize } from './redux_embeddables/clean_redux_embeddable_state';
export type { ReduxToolsPackage, ReduxTools } from './types';
export const lazyLoadReduxToolsPackage = async (): Promise<ReduxToolsPackage> => {
const { createReduxTools } = await import('./create_redux_tools');
const { createReduxEmbeddableTools } = await import(
'./redux_embeddables/create_redux_embeddable_tools'
);
return {
createReduxTools,
createReduxEmbeddableTools,
};
};

View file

@ -1,129 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { EmbeddableInput } from '@kbn/embeddable-plugin/public';
import { Filter } from '@kbn/es-query';
import {
cleanFiltersForSerialize,
cleanInputForRedux,
cleanStateForRedux,
stateContainsFilters,
} from './clean_redux_embeddable_state';
type InputWithFilters = Partial<EmbeddableInput> & { filters: Filter[] };
describe('stateContainsFilters', () => {
test('should return true if state contains filters', () => {
const explicitInput: InputWithFilters = {
id: 'wat',
filters: [{ query: {}, meta: {} }],
};
expect(stateContainsFilters(explicitInput)).toBe(true);
});
test('should return false if state does not contain filters', () => {
const explicitInput: EmbeddableInput = {
id: 'wat',
};
expect(stateContainsFilters(explicitInput)).toBe(false);
});
});
describe('cleanFiltersForSerialize', () => {
test('should return an empty array if filters is not provided', () => {
expect(cleanFiltersForSerialize()).toEqual([]);
});
test('should remove "meta.value" property from each filter', () => {
const filters: Filter[] = [
{ query: { a: 'a' }, meta: { value: 'value1' } },
{ query: { b: 'b' }, meta: { value: 'value2' } },
];
const cleanedFilters = cleanFiltersForSerialize(filters);
expect(cleanedFilters[0]).toEqual({ query: { a: 'a' }, meta: {} });
expect(cleanedFilters[1]).toEqual({ query: { b: 'b' }, meta: {} });
});
test('should not fail if meta is missing from filters', () => {
const filters: Filter[] = [{ query: { a: 'a' } }, { query: { b: 'b' } }] as unknown as Filter[];
const cleanedFilters = cleanFiltersForSerialize(filters as unknown as Filter[]);
expect(cleanedFilters[0]).toEqual({ query: { a: 'a' } });
expect(cleanedFilters[1]).toEqual({ query: { b: 'b' } });
});
});
describe('cleanInputForRedux', () => {
test('should clean filters to make explicit input serializable', () => {
const explicitInput = {
id: 'wat',
filters: [
{ query: { a: 'a' }, meta: { value: 'value1' } },
{ query: { b: 'b' }, meta: { value: 'value2' } },
],
};
const cleanedInput = cleanInputForRedux(explicitInput) as InputWithFilters;
expect(cleanedInput.filters[0]).toEqual({ query: { a: 'a' }, meta: {} });
expect(cleanedInput.filters[1]).toEqual({ query: { b: 'b' }, meta: {} });
});
test('should not modify input if filters are not present', () => {
const explicitInput = {
id: 'wat',
otherProp: 'value',
};
const cleanedInput = cleanInputForRedux(explicitInput);
expect(cleanedInput).toEqual(explicitInput);
});
});
describe('cleanStateForRedux', () => {
test('should clean explicitInput for serializable state', () => {
const state = {
output: {},
componentState: {},
explicitInput: {
id: 'wat',
filters: [
{ query: { a: 'a' }, meta: { value: 'value1' } },
{ query: { b: 'b' }, meta: { value: 'value2' } },
],
},
};
const cleanedState = cleanStateForRedux(state) as { explicitInput: InputWithFilters };
expect(cleanedState.explicitInput.filters[0]).toEqual({ query: { a: 'a' }, meta: {} });
expect(cleanedState.explicitInput.filters[1]).toEqual({ query: { b: 'b' }, meta: {} });
});
test('should not modify state if explicitInput filters are not present', () => {
const state = {
output: {},
componentState: {},
explicitInput: {
id: 'wat',
otherKey: 'value',
},
};
const cleanedState = cleanStateForRedux(state);
expect(cleanedState).toEqual(state);
});
});

View file

@ -1,52 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Filter } from '@kbn/es-query';
import { EmbeddableInput } from '@kbn/embeddable-plugin/public';
import { ReduxEmbeddableState } from './types';
// TODO: Make filters serializable so we don't need special treatment for them.
type InputWithFilters = Partial<EmbeddableInput> & { filters: Filter[] };
export const stateContainsFilters = (
state: Partial<EmbeddableInput>
): state is InputWithFilters => {
if ((state as InputWithFilters).filters && (state as InputWithFilters).filters.length > 0)
return true;
return false;
};
export const cleanFiltersForSerialize = (filters?: Filter[]): Filter[] => {
if (!filters) return [];
return filters.map((filter) => {
if (filter.meta?.value) delete filter.meta.value;
return filter;
});
};
export const cleanInputForRedux = <
ReduxEmbeddableStateType extends ReduxEmbeddableState = ReduxEmbeddableState
>(
explicitInput: ReduxEmbeddableStateType['explicitInput']
) => {
if (stateContainsFilters(explicitInput)) {
explicitInput.filters = cleanFiltersForSerialize(explicitInput.filters);
}
return explicitInput;
};
export const cleanStateForRedux = <
ReduxEmbeddableStateType extends ReduxEmbeddableState = ReduxEmbeddableState
>(
state: ReduxEmbeddableStateType
) => {
// clean explicit input
state.explicitInput = cleanInputForRedux<ReduxEmbeddableStateType>(state.explicitInput);
return state;
};

View file

@ -1,98 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { Draft, AnyAction, Middleware, PayloadAction } from '@reduxjs/toolkit';
import { Embeddable } from '@kbn/embeddable-plugin/public';
import { ReduxToolsReducers } from '../types';
import { createReduxTools } from '../create_redux_tools';
import { syncReduxEmbeddable } from './sync_redux_embeddable';
import { cleanStateForRedux } from './clean_redux_embeddable_state';
import { ReduxEmbeddableTools, ReduxEmbeddableState, ReduxEmbeddableSyncSettings } from './types';
export const createReduxEmbeddableTools = <
ReduxEmbeddableStateType extends ReduxEmbeddableState = ReduxEmbeddableState,
ReducerType extends ReduxToolsReducers<ReduxEmbeddableStateType> = ReduxToolsReducers<ReduxEmbeddableStateType>
>({
reducers,
embeddable,
syncSettings,
additionalMiddleware,
initialComponentState,
}: {
embeddable: Embeddable<
ReduxEmbeddableStateType['explicitInput'],
ReduxEmbeddableStateType['output']
>;
additionalMiddleware?: Array<Middleware<AnyAction>>;
initialComponentState?: ReduxEmbeddableStateType['componentState'];
syncSettings?: ReduxEmbeddableSyncSettings;
reducers: ReducerType;
}): ReduxEmbeddableTools<ReduxEmbeddableStateType, ReducerType> => {
/**
* Build additional generic reducers to aid in embeddable syncing.
*/
const genericReducers = {
replaceEmbeddableReduxInput: (
state: Draft<ReduxEmbeddableStateType>,
action: PayloadAction<ReduxEmbeddableStateType['explicitInput']>
) => {
state.explicitInput = action.payload;
},
replaceEmbeddableReduxOutput: (
state: Draft<ReduxEmbeddableStateType>,
action: PayloadAction<ReduxEmbeddableStateType['output']>
) => {
state.output = action.payload;
},
};
const allReducers = { ...reducers, ...genericReducers };
/**
* Create initial state from Embeddable.
*/
let initialState: ReduxEmbeddableStateType = {
output: embeddable.getOutput(),
componentState: initialComponentState ?? {},
explicitInput: embeddable.getExplicitInput(),
} as ReduxEmbeddableStateType;
initialState = cleanStateForRedux<ReduxEmbeddableStateType>(initialState);
const { dispatch, store, select, getState, onStateChange } = createReduxTools<
ReduxEmbeddableStateType,
typeof allReducers
>({
reducers: allReducers,
additionalMiddleware,
initialState,
});
/**
* Sync redux state with embeddable input and output observables. Eventually we can replace the input and output observables
* with redux and remove this sync.
*/
const stopReduxEmbeddableSync = syncReduxEmbeddable<ReduxEmbeddableStateType>({
replaceEmbeddableReduxInput: dispatch.replaceEmbeddableReduxInput,
replaceEmbeddableReduxOutput: dispatch.replaceEmbeddableReduxOutput,
settings: syncSettings,
embeddable,
store,
});
return {
store,
select,
dispatch,
getState,
onStateChange,
cleanup: () => stopReduxEmbeddableSync?.(),
};
};

View file

@ -1,107 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import deepEqual from 'fast-deep-equal';
import { EnhancedStore } from '@reduxjs/toolkit';
import { IEmbeddable } from '@kbn/embeddable-plugin/public';
import { cleanInputForRedux } from './clean_redux_embeddable_state';
import { ReduxEmbeddableState, ReduxEmbeddableSyncSettings } from './types';
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
export const syncReduxEmbeddable = <
ReduxEmbeddableStateType extends ReduxEmbeddableState = ReduxEmbeddableState
>({
store,
settings,
embeddable,
replaceEmbeddableReduxInput,
replaceEmbeddableReduxOutput,
}: {
settings?: ReduxEmbeddableSyncSettings;
store: EnhancedStore<ReduxEmbeddableStateType>;
replaceEmbeddableReduxInput: (input: ReduxEmbeddableStateType['explicitInput']) => void;
replaceEmbeddableReduxOutput: (output: ReduxEmbeddableStateType['output']) => void;
embeddable: IEmbeddable<
ReduxEmbeddableStateType['explicitInput'],
ReduxEmbeddableStateType['output']
>;
}) => {
if (settings?.disableSync) {
return;
}
let embeddableToReduxInProgress = false;
let reduxToEmbeddableInProgress = false;
const { isInputEqual: inputEqualityCheck, isOutputEqual: outputEqualityCheck } = settings ?? {};
const inputEqual = (
inputA: Partial<ReduxEmbeddableStateType['explicitInput']>,
inputB: Partial<ReduxEmbeddableStateType['explicitInput']>
) => (inputEqualityCheck ? inputEqualityCheck(inputA, inputB) : deepEqual(inputA, inputB));
const outputEqual = (
outputA: ReduxEmbeddableStateType['output'],
outputB: ReduxEmbeddableStateType['output']
) => (outputEqualityCheck ? outputEqualityCheck(outputA, outputB) : deepEqual(outputA, outputB));
// when the redux store changes, diff, and push updates to the embeddable input or to the output.
const unsubscribeFromStore = store.subscribe(() => {
if (embeddableToReduxInProgress) return;
reduxToEmbeddableInProgress = true;
const reduxState = store.getState();
if (!inputEqual(reduxState.explicitInput, embeddable.getExplicitInput())) {
embeddable.updateInput(reduxState.explicitInput);
}
if (!outputEqual(reduxState.output, embeddable.getOutput())) {
// updating output is usually not accessible from outside of the embeddable.
// This redux sync utility is meant to be used from inside the embeddable, so we need to workaround the typescript error via casting.
(
embeddable as unknown as {
updateOutput: (newOutput: ReduxEmbeddableStateType['output']) => void;
}
).updateOutput(reduxState.output);
}
reduxToEmbeddableInProgress = false;
});
// when the embeddable input changes, diff and dispatch to the redux store
const inputSubscription = embeddable.getInput$().subscribe(() => {
if (reduxToEmbeddableInProgress) return;
embeddableToReduxInProgress = true;
const { explicitInput: reduxExplicitInput } = store.getState();
// store only explicit input in the store
const embeddableExplictInput = embeddable.getExplicitInput() as Writeable<
ReduxEmbeddableStateType['explicitInput']
>;
if (!inputEqual(reduxExplicitInput, embeddableExplictInput)) {
replaceEmbeddableReduxInput(cleanInputForRedux(embeddableExplictInput));
}
embeddableToReduxInProgress = false;
});
// when the embeddable output changes, diff and dispatch to the redux store
const outputSubscription = embeddable.getOutput$().subscribe((embeddableOutput) => {
if (reduxToEmbeddableInProgress) return;
embeddableToReduxInProgress = true;
const reduxState = store.getState();
if (!outputEqual(reduxState.output, embeddableOutput)) {
replaceEmbeddableReduxOutput(embeddableOutput);
}
embeddableToReduxInProgress = false;
});
return () => {
unsubscribeFromStore();
inputSubscription.unsubscribe();
outputSubscription.unsubscribe();
};
};

View file

@ -1,57 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { EnhancedStore } from '@reduxjs/toolkit';
import { EmbeddableInput, EmbeddableOutput } from '@kbn/embeddable-plugin/public';
import { ReduxToolsReducers, ReduxToolsSelect, ReduxToolsSetters } from '../types';
export interface ReduxEmbeddableSyncSettings<
ReduxEmbeddableStateType extends ReduxEmbeddableState = ReduxEmbeddableState
> {
disableSync: boolean;
isInputEqual?: (
a: Partial<ReduxEmbeddableStateType['explicitInput']>,
b: Partial<ReduxEmbeddableStateType['explicitInput']>
) => boolean;
isOutputEqual?: (
a: Partial<ReduxEmbeddableStateType['output']>,
b: Partial<ReduxEmbeddableStateType['output']>
) => boolean;
}
/**
* The return type from createReduxEmbeddableTools. Contains tools to get state, select state for react components,
* set state, and react to state changes.
*/
export interface ReduxEmbeddableTools<
ReduxEmbeddableStateType extends ReduxEmbeddableState = ReduxEmbeddableState,
ReducerType extends ReduxToolsReducers<ReduxEmbeddableStateType> = ReduxToolsReducers<ReduxEmbeddableStateType>
> {
cleanup: () => void;
store: EnhancedStore<ReduxEmbeddableStateType>;
select: ReduxToolsSelect<ReduxEmbeddableStateType>;
getState: EnhancedStore<ReduxEmbeddableStateType>['getState'];
dispatch: ReduxToolsSetters<ReduxEmbeddableStateType, ReducerType>;
onStateChange: EnhancedStore<ReduxEmbeddableStateType>['subscribe'];
}
/**
* The Embeddable Redux store should contain Input, Output and State. Input is serialized and used to create the embeddable,
* Output is used as a communication layer for state that the Embeddable creates, and State is used to store ephemeral state which needs
* to be communicated between an embeddable and its inner React components.
*/
export interface ReduxEmbeddableState<
InputType extends EmbeddableInput = EmbeddableInput,
OutputType extends EmbeddableOutput = EmbeddableOutput,
StateType extends unknown = unknown
> {
explicitInput: InputType;
output: OutputType;
componentState: StateType;
}

View file

@ -1,66 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { CaseReducer, PayloadAction, EnhancedStore } from '@reduxjs/toolkit';
/**
* The Redux Tools Selector is a react redux selector function that can be used to grab values from the state, and to make a component
* re-render when those values change.
*/
export type ReduxToolsSelect<ReduxStateType extends unknown> = <Selected extends unknown>(
selector: (state: ReduxStateType) => Selected,
equalityFn?: ((previous: Selected, next: Selected) => boolean) | undefined
) => Selected;
/**
* The Redux Embeddable Setters are a collection of functions which dispatch actions to the correct store.
*/
export type ReduxToolsSetters<
ReduxStateType extends unknown,
ReducerType extends ReduxToolsReducers<ReduxStateType> = ReduxToolsReducers<ReduxStateType>
> = {
[ReducerKey in keyof ReducerType]: (
payload: Parameters<ReducerType[ReducerKey]>[1]['payload']
) => void;
};
/**
* The return type from createReduxTools. Contains tools to get state, select state for react components,
* set state, and react to state changes.
*/
export interface ReduxTools<
ReduxStateType extends unknown,
ReducerType extends ReduxToolsReducers<ReduxStateType> = ReduxToolsReducers<ReduxStateType>
> {
store: EnhancedStore<ReduxStateType>;
select: ReduxToolsSelect<ReduxStateType>;
getState: EnhancedStore<ReduxStateType>['getState'];
onStateChange: EnhancedStore<ReduxStateType>['subscribe'];
dispatch: ReduxToolsSetters<ReduxStateType, ReducerType>;
}
/**
* The Redux Tools Reducers are the shape of the Raw reducers which will be passed into createSlice. These will be used to populate the actions
* object which the tools will return.
*/
export interface ReduxToolsReducers<ReduxStateType extends unknown> {
/**
* PayloadAction of type any is strategic here because we want to allow payloads of any shape in generic reducers.
* This type will be overridden to remove any and be type safe when returned by setupReduxEmbeddable.
*/
[key: string]: CaseReducer<ReduxStateType, PayloadAction<any>>;
}
/**
* The package type is lazily exported from presentation_util and should contain all methods needed to use the redux embeddable tools.
*/
export interface ReduxToolsPackage {
createReduxTools: typeof import('./create_redux_tools')['createReduxTools'];
createReduxEmbeddableTools: typeof import('./redux_embeddables/create_redux_embeddable_tools')['createReduxEmbeddableTools'];
}

View file

@ -22,7 +22,6 @@
"@kbn/data-views-plugin",
"@kbn/i18n-react",
"@kbn/monaco",
"@kbn/es-query",
"@kbn/field-formats-plugin",
"@kbn/interpreter",
"@kbn/react-field",