[Control group] Open "add control editor" with last used width and grow settings (#190749)

### test instructions
1. run kibana with `yarn start --run-examples`
2. navigate to the "Controls" developer example
3. Click "Add new data-control" button. Notice how the menu sets "width"
to medium and selects "grow".
4. Add new control, setting "width" to small and unselected "grow".
Click "Save and close".
5. Click "Add new data-control" button. Notice how the menu sets "width"
and "grow" to last used values.

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2024-08-21 18:43:38 -06:00 committed by GitHub
parent f512acf9c3
commit 01aae2331d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 78 additions and 51 deletions

View file

@ -27,11 +27,8 @@ import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetc
import { ControlStyle, ParentIgnoreSettings } from '../..';
import {
ControlGroupChainingSystem,
ControlWidth,
CONTROL_GROUP_TYPE,
DEFAULT_CONTROL_GROW,
DEFAULT_CONTROL_STYLE,
DEFAULT_CONTROL_WIDTH,
} from '../../../common';
import { chaining$, controlFetch$, controlGroupFetch$ } from './control_fetch';
import { initControlsManager } from './init_controls_manager';
@ -64,8 +61,6 @@ export const getControlGroupEmbeddableFactory = (services: {
) => {
const {
initialChildControlState,
defaultControlGrow,
defaultControlWidth,
labelPosition: initialLabelPosition,
chainingSystem,
autoApplySelections,
@ -89,12 +84,6 @@ export const getControlGroupEmbeddableFactory = (services: {
const ignoreParentSettings$ = new BehaviorSubject<ParentIgnoreSettings | undefined>(
ignoreParentSettings
);
const grow = new BehaviorSubject<boolean | undefined>(
defaultControlGrow === undefined ? DEFAULT_CONTROL_GROW : defaultControlGrow
);
const width = new BehaviorSubject<ControlWidth | undefined>(
defaultControlWidth ?? DEFAULT_CONTROL_WIDTH
);
const labelPosition$ = new BehaviorSubject<ControlStyle>( // TODO: Rename `ControlStyle`
initialLabelPosition ?? DEFAULT_CONTROL_STYLE // TODO: Rename `DEFAULT_CONTROL_STYLE`
);
@ -175,13 +164,9 @@ export const getControlGroupEmbeddableFactory = (services: {
controlInputTransform: (state) => state,
};
openDataControlEditor({
initialState: {
grow: api.grow.getValue(),
width: api.width.getValue(),
dataViewId: controlsManager.api.lastUsedDataViewId$.value,
},
initialState: controlsManager.getNewControlState(),
onSave: ({ type: controlType, state: initialState }) => {
api.addNewPanel({
controlsManager.api.addNewPanel({
panelType: controlType,
initialState: controlInputTransform!(
initialState as Partial<ControlGroupSerializedState>,
@ -206,8 +191,6 @@ export const getControlGroupEmbeddableFactory = (services: {
references,
};
},
grow,
width,
dataViews,
labelPosition: labelPosition$,
saveNotification$: apiHasSaveNotification(parentApi)

View file

@ -6,8 +6,10 @@
* Side Public License, v 1.
*/
import { DefaultDataControlState } from '../controls/data_controls/types';
import { DefaultControlApi } from '../controls/types';
import { initControlsManager, getLastUsedDataViewId } from './init_controls_manager';
import { ControlPanelState } from './types';
jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('delta'),
@ -187,3 +189,51 @@ describe('getLastUsedDataViewId', () => {
expect(dataViewId).toBeUndefined();
});
});
describe('getNewControlState', () => {
test('should contain defaults when there are no existing controls', () => {
const controlsManager = initControlsManager({}, DEFAULT_DATA_VIEW_ID);
expect(controlsManager.getNewControlState()).toEqual({
grow: true,
width: 'medium',
dataViewId: DEFAULT_DATA_VIEW_ID,
});
});
test('should start with defaults if there are existing controls', () => {
const controlsManager = initControlsManager(
{
alpha: {
type: 'testControl',
order: 1,
dataViewId: 'myOtherDataViewId',
width: 'small',
grow: false,
} as ControlPanelState & Pick<DefaultDataControlState, 'dataViewId'>,
},
DEFAULT_DATA_VIEW_ID
);
expect(controlsManager.getNewControlState()).toEqual({
grow: true,
width: 'medium',
dataViewId: 'myOtherDataViewId',
});
});
test('should contain values of last added control', () => {
const controlsManager = initControlsManager({}, DEFAULT_DATA_VIEW_ID);
controlsManager.api.addNewPanel({
panelType: 'testControl',
initialState: {
grow: false,
width: 'small',
dataViewId: 'myOtherDataViewId',
},
});
expect(controlsManager.getNewControlState()).toEqual({
grow: false,
width: 'small',
dataViewId: 'myOtherDataViewId',
});
});
});

View file

@ -22,6 +22,7 @@ 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';
import { ControlWidth, DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../common';
export type ControlsInOrder = Array<{ id: string; type: string }>;
@ -54,6 +55,8 @@ export function initControlsManager(
defaultDataViewId ??
undefined
);
const lastUsedWidth$ = new BehaviorSubject<ControlWidth>(DEFAULT_CONTROL_WIDTH);
const lastUsedGrow$ = new BehaviorSubject<boolean>(DEFAULT_CONTROL_GROW);
function untilControlLoaded(
id: string
@ -91,6 +94,13 @@ export function initControlsManager(
if ((initialState as DefaultDataControlState)?.dataViewId) {
lastUsedDataViewId$.next((initialState as DefaultDataControlState).dataViewId);
}
if (initialState?.width) {
lastUsedWidth$.next(initialState.width);
}
if (typeof initialState?.grow === 'boolean') {
lastUsedGrow$.next(initialState.grow);
}
const id = generateId();
const nextControlsInOrder = [...controlsInOrder$.value];
nextControlsInOrder.splice(index, 0, {
@ -110,6 +120,13 @@ export function initControlsManager(
return {
controlsInOrder$,
getNewControlState: () => {
return {
grow: lastUsedGrow$.value,
width: lastUsedWidth$.value,
dataViewId: lastUsedDataViewId$.value,
};
},
getControlApi,
setControlApi: (uuid: string, controlApi: DefaultControlApi) => {
children$.next({
@ -168,7 +185,6 @@ export function initControlsManager(
return controlsRuntimeState;
},
api: {
lastUsedDataViewId$: lastUsedDataViewId$ as PublishingSubject<string | undefined>,
getSerializedStateForChild: (childId: string) => {
const controlPanelState = controlsPanelState[childId];
return controlPanelState ? { rawState: controlPanelState } : undefined;
@ -210,7 +226,7 @@ export function initControlsManager(
},
} as PresentationContainer &
HasSerializedChildState<ControlPanelState> &
Pick<ControlGroupApi, 'untilInitialized' | 'lastUsedDataViewId$'>,
Pick<ControlGroupApi, 'untilInitialized'>,
comparators: {
controlsInOrder: [
controlsInOrder$,

View file

@ -8,7 +8,6 @@
import { SerializedPanelState } from '@kbn/presentation-containers';
import { omit } from 'lodash';
import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../common';
import { ControlGroupRuntimeState, ControlGroupSerializedState } from './types';
export const deserializeControlGroup = (
@ -46,7 +45,5 @@ export const deserializeControlGroup = (
? !state.rawState.showApplySelections
: false, // Rename "showApplySelections" to "autoApplySelections"
labelPosition: state.rawState.controlStyle, // Rename "controlStyle" to "labelPosition"
defaultControlGrow: DEFAULT_CONTROL_GROW,
defaultControlWidth: DEFAULT_CONTROL_WIDTH,
};
};

View file

@ -31,21 +31,17 @@ import { PublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/p
import { ParentIgnoreSettings } from '../..';
import { ControlInputTransform } from '../../../common';
import { ControlGroupChainingSystem } from '../../../common/control_group/types';
import { ControlStyle, ControlWidth } from '../../types';
import { DefaultControlState, PublishesControlDisplaySettings } from '../controls/types';
import { ControlStyle } from '../../types';
import { DefaultControlState } from '../controls/types';
import { ControlFetchContext } from './control_fetch/control_fetch';
/** The control display settings published by the control group are the "default" */
type PublishesControlGroupDisplaySettings = PublishesControlDisplaySettings & {
labelPosition: PublishingSubject<ControlStyle>;
};
export interface ControlPanelsState<ControlState extends ControlPanelState = ControlPanelState> {
[panelId: string]: ControlState;
}
export type ControlGroupUnsavedChanges = Omit<
ControlGroupRuntimeState,
'initialChildControlState' | 'defaultControlGrow' | 'defaultControlWidth'
'initialChildControlState'
> & {
filters: Filter[] | undefined;
};
@ -60,7 +56,6 @@ export type ControlGroupApi = PresentationContainer &
HasEditCapabilities &
PublishesDataLoading &
Pick<PublishesUnsavedChanges, 'unsavedChanges'> &
PublishesControlGroupDisplaySettings &
PublishesTimeslice &
Partial<HasParentApi<PublishesUnifiedSearch> & HasSaveNotification & PublishesReload> & {
asyncResetUnsavedChanges: () => Promise<void>;
@ -73,13 +68,11 @@ export type ControlGroupApi = PresentationContainer &
openAddDataControlFlyout: (settings?: {
controlInputTransform?: ControlInputTransform;
}) => void;
lastUsedDataViewId$: PublishingSubject<string | undefined>;
labelPosition: PublishingSubject<ControlStyle>;
};
export interface ControlGroupRuntimeState {
chainingSystem: ControlGroupChainingSystem;
defaultControlGrow?: boolean;
defaultControlWidth?: ControlWidth;
labelPosition: ControlStyle; // TODO: Rename this type to ControlLabelPosition
autoApplySelections: boolean;
ignoreParentSettings?: ParentIgnoreSettings;

View file

@ -32,7 +32,6 @@ import {
} from '@elastic/eui';
import { DataViewField } from '@kbn/data-views-plugin/common';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import {
LazyDataViewPicker,
LazyFieldPicker,
@ -145,10 +144,6 @@ export const DataControlEditor = <State extends DefaultDataControlState = Defaul
/** TODO: These should not be props */
services: { dataViews: dataViewService },
}: ControlEditorProps<State>) => {
const [defaultGrow, defaultWidth] = useBatchedPublishingSubjects(
controlGroupApi.grow,
controlGroupApi.width
);
const [editorState, setEditorState] = useState<Partial<State>>(initialState);
const [defaultPanelTitle, setDefaultPanelTitle] = useState<string>(
initialDefaultPanelTitle ?? initialState.fieldName ?? ''
@ -368,7 +363,7 @@ export const DataControlEditor = <State extends DefaultDataControlState = Defaul
color="primary"
legend={DataControlEditorStrings.management.controlWidth.getWidthSwitchLegend()}
options={CONTROL_WIDTH_OPTIONS}
idSelected={editorState.width ?? defaultWidth ?? DEFAULT_CONTROL_WIDTH}
idSelected={editorState.width ?? DEFAULT_CONTROL_WIDTH}
onChange={(newWidth: string) =>
setEditorState({ ...editorState, width: newWidth as ControlWidth })
}
@ -377,10 +372,7 @@ export const DataControlEditor = <State extends DefaultDataControlState = Defaul
<EuiSwitch
label={DataControlEditorStrings.manageControl.displaySettings.getGrowSwitchTitle()}
color="primary"
checked={
(editorState.grow === undefined ? defaultGrow : editorState.grow) ??
DEFAULT_CONTROL_GROW
}
checked={editorState.grow ?? DEFAULT_CONTROL_GROW}
onChange={() => setEditorState({ ...editorState, grow: !editorState.grow })}
data-test-subj="control-editor-grow-switch"
/>

View file

@ -26,11 +26,6 @@ import { CanClearSelections, ControlWidth } from '../../types';
import { ControlGroupApi } from '../control_group/types';
export interface PublishesControlDisplaySettings {
grow: PublishingSubject<boolean | undefined>;
width: PublishingSubject<ControlWidth | undefined>;
}
export interface HasCustomPrepend {
CustomPrependComponent: React.FC<{}>;
}
@ -38,7 +33,6 @@ export interface HasCustomPrepend {
export type DefaultControlApi = PublishesDataLoading &
PublishesBlockingError &
PublishesUnsavedChanges &
PublishesControlDisplaySettings &
Partial<PublishesPanelTitle & PublishesDisabledActionIds & HasCustomPrepend> &
CanClearSelections &
HasType &
@ -51,6 +45,8 @@ export type DefaultControlApi = PublishesDataLoading &
/** TODO: Make these non-public as part of https://github.com/elastic/kibana/issues/174961 */
setDataLoading: (loading: boolean) => void;
setBlockingError: (error: Error | undefined) => void;
grow: PublishingSubject<boolean | undefined>;
width: PublishingSubject<ControlWidth | undefined>;
};
export interface DefaultControlState {