mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Embeddable Rebuild] [Controls] Add order
to control factory (#189670)
Closes https://github.com/elastic/kibana/issues/189407 ## Summary This PR adds an `order` attribute to the control factory so that the ordering in the UI remains consistent - previously, the order was determined by the order the factories were registered in (which is no longer predictable now that the registration happens `async` - it's hard to repro, but there were times where something delayed the options list registration and it would appear at the end of my list). Adding and sorting the UI based on the `order` of the factory removes this uncertainty. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
73d85c38c4
commit
e5cb696f47
4 changed files with 81 additions and 12 deletions
|
@ -32,7 +32,7 @@ import {
|
|||
getMockedSearchControlFactory,
|
||||
} from './mocks/factory_mocks';
|
||||
import { ControlFactory } from '../types';
|
||||
import { DataControlApi, DefaultDataControlState } from './types';
|
||||
import { DataControlApi, DataControlFactory, DefaultDataControlState } from './types';
|
||||
|
||||
const mockDataViews = dataViewPluginMocks.createStartContract();
|
||||
const mockDataView = createStubDataView({
|
||||
|
@ -106,13 +106,13 @@ describe('Data control editor', () => {
|
|||
return controlEditor.getByTestId(testId).getAttribute('aria-pressed');
|
||||
};
|
||||
|
||||
const mockRegistry: { [key: string]: ControlFactory<DefaultDataControlState, DataControlApi> } = {
|
||||
search: getMockedSearchControlFactory({ parentApi: controlGroupApi }),
|
||||
optionsList: getMockedOptionsListControlFactory({ parentApi: controlGroupApi }),
|
||||
rangeSlider: getMockedRangeSliderControlFactory({ parentApi: controlGroupApi }),
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
const mockRegistry: { [key: string]: ControlFactory<DefaultDataControlState, DataControlApi> } =
|
||||
{
|
||||
search: getMockedSearchControlFactory({ parentApi: controlGroupApi }),
|
||||
optionsList: getMockedOptionsListControlFactory({ parentApi: controlGroupApi }),
|
||||
rangeSlider: getMockedRangeSliderControlFactory({ parentApi: controlGroupApi }),
|
||||
};
|
||||
(getAllControlTypes as jest.Mock).mockReturnValue(Object.keys(mockRegistry));
|
||||
(getControlFactory as jest.Mock).mockImplementation((key) => mockRegistry[key]);
|
||||
});
|
||||
|
@ -133,6 +133,50 @@ describe('Data control editor', () => {
|
|||
expect(saveButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test('CompatibleControlTypesComponent respects ordering', async () => {
|
||||
const tempRegistry: {
|
||||
[key: string]: ControlFactory<DefaultDataControlState, DataControlApi>;
|
||||
} = {
|
||||
...mockRegistry,
|
||||
alphabeticalFirstControl: {
|
||||
type: 'alphabeticalFirst',
|
||||
getIconType: () => 'lettering',
|
||||
getDisplayName: () => 'Alphabetically first',
|
||||
isFieldCompatible: () => true,
|
||||
buildControl: jest.fn().mockReturnValue({
|
||||
api: controlGroupApi,
|
||||
Component: <>Should be first alphabetically</>,
|
||||
}),
|
||||
} as DataControlFactory,
|
||||
supremeControl: {
|
||||
type: 'supremeControl',
|
||||
order: 100, // force it first despite alphabetical ordering
|
||||
getIconType: () => 'starFilled',
|
||||
getDisplayName: () => 'Supreme leader',
|
||||
isFieldCompatible: () => true,
|
||||
buildControl: jest.fn().mockReturnValue({
|
||||
api: controlGroupApi,
|
||||
Component: <>This control is forced first via the factory order</>,
|
||||
}),
|
||||
} as DataControlFactory,
|
||||
};
|
||||
(getAllControlTypes as jest.Mock).mockReturnValue(Object.keys(tempRegistry));
|
||||
(getControlFactory as jest.Mock).mockImplementation((key) => tempRegistry[key]);
|
||||
|
||||
const controlEditor = await mountComponent({});
|
||||
const menu = controlEditor.getByTestId('controlTypeMenu');
|
||||
expect(menu.children.length).toEqual(5);
|
||||
expect(menu.children[0].textContent).toEqual('Supreme leader'); // forced first - ignore alphabetical sorting
|
||||
// the rest should be alphabetically sorted
|
||||
expect(menu.children[1].textContent).toEqual('Alphabetically first');
|
||||
expect(menu.children[2].textContent).toEqual('Options list');
|
||||
expect(menu.children[3].textContent).toEqual('Range slider');
|
||||
expect(menu.children[4].textContent).toEqual('Search');
|
||||
|
||||
(getAllControlTypes as jest.Mock).mockReturnValue(Object.keys(mockRegistry));
|
||||
(getControlFactory as jest.Mock).mockImplementation((key) => mockRegistry[key]);
|
||||
});
|
||||
|
||||
test('selecting a keyword field - can only create an options list control', async () => {
|
||||
const controlEditor = await mountComponent({});
|
||||
await selectField(controlEditor, 'machine.os.raw');
|
||||
|
|
|
@ -80,9 +80,18 @@ const CompatibleControlTypesComponent = ({
|
|||
const dataControlFactories = useMemo(() => {
|
||||
return getAllControlTypes()
|
||||
.map((type) => getControlFactory(type))
|
||||
.filter((factory) => {
|
||||
return isDataControlFactory(factory);
|
||||
});
|
||||
.filter((factory) => isDataControlFactory(factory))
|
||||
.sort(
|
||||
(
|
||||
{ order: orderA = 0, getDisplayName: getDisplayNameA },
|
||||
{ order: orderB = 0, getDisplayName: getDisplayNameB }
|
||||
) => {
|
||||
const orderComparison = orderB - orderA; // sort descending by order
|
||||
return orderComparison === 0
|
||||
? getDisplayNameA().localeCompare(getDisplayNameB()) // if equal order, compare display names
|
||||
: orderComparison;
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -283,8 +292,23 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon
|
|||
dataView={selectedDataView}
|
||||
onSelectField={(field) => {
|
||||
setEditorState({ ...editorState, fieldName: field.name });
|
||||
setSelectedControlType(fieldRegistry?.[field.name]?.compatibleControlTypes[0]);
|
||||
|
||||
/**
|
||||
* make sure that the new field is compatible with the selected control type and, if it's not,
|
||||
* reset the selected control type to the **first** compatible control type
|
||||
*/
|
||||
const newCompatibleControlTypes =
|
||||
fieldRegistry?.[field.name]?.compatibleControlTypes ?? [];
|
||||
if (
|
||||
!selectedControlType ||
|
||||
!newCompatibleControlTypes.includes(selectedControlType!)
|
||||
) {
|
||||
setSelectedControlType(newCompatibleControlTypes[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the control title (i.e. the one set by the user) + default title (i.e. the field display name)
|
||||
*/
|
||||
const newDefaultTitle = field.displayName ?? field.name;
|
||||
setDefaultPanelTitle(newDefaultTitle);
|
||||
const currentTitle = editorState.title;
|
||||
|
@ -365,7 +389,6 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon
|
|||
{/* )} */}
|
||||
</EuiDescribedFormGroup>
|
||||
{CustomSettingsComponent}
|
||||
{/* {!editorConfig?.hideAdditionalSettings ? CustomSettingsComponent : null} */}
|
||||
{initialState.controlId && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
|
|
|
@ -44,6 +44,7 @@ export const getOptionsListControlFactory = (
|
|||
): DataControlFactory<OptionsListControlState, OptionsListControlApi> => {
|
||||
return {
|
||||
type: OPTIONS_LIST_CONTROL_TYPE,
|
||||
order: 3, // should always be first, since this is the most popular control
|
||||
getIconType: () => 'editorChecklist',
|
||||
getDisplayName: OptionsListStrings.control.getDisplayName,
|
||||
isFieldCompatible: (field) => {
|
||||
|
|
|
@ -75,6 +75,7 @@ export interface ControlFactory<
|
|||
ControlApi extends DefaultControlApi = DefaultControlApi
|
||||
> {
|
||||
type: string;
|
||||
order?: number;
|
||||
getIconType: () => string;
|
||||
getDisplayName: () => string;
|
||||
buildControl: (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue