Skip to content

Commit

Permalink
[Dashboard] [Controls] Refactor options list to align with new code s…
Browse files Browse the repository at this point in the history
…tandards (#138336)

* Refactor public folder structure

* Refactor common folder structure

* Refactor server folder structure

* Stub out Jest tests and stories

* Add popover jest tests

* Fix strings + imports

* Remove comments and unused strings

* Fix jest tests

* Clean imports
  • Loading branch information
Heenawter authored Aug 12, 2022
1 parent 4ddc26d commit e0085c4
Show file tree
Hide file tree
Showing 34 changed files with 316 additions and 175 deletions.
47 changes: 47 additions & 0 deletions src/plugins/controls/common/control_group/mocks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 { getDefaultControlGroupInput } from '..';
import { ControlGroupInput } from './types';

export const mockControlGroupInput = (partial?: Partial<ControlGroupInput>): ControlGroupInput => ({
id: 'mocked_control_group',
...getDefaultControlGroupInput(),
...{
panels: {
control1: {
order: 0,
width: 'medium',
grow: true,
type: 'mockedOptionsList',
explicitInput: {
id: 'control1',
},
},
control2: {
order: 1,
width: 'large',
grow: true,
type: 'mockedRangeSlider',
explicitInput: {
id: 'control2',
},
},
control3: {
order: 2,
width: 'small',
grow: true,
type: 'mockedOptionsList',
explicitInput: {
id: 'control3',
},
},
},
},
...(partial ?? {}),
});
5 changes: 1 addition & 4 deletions src/plugins/controls/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ export {
} from './control_group/control_group_constants';

// Control Type exports
export {
OPTIONS_LIST_CONTROL,
type OptionsListEmbeddableInput,
} from './control_types/options_list/types';
export { OPTIONS_LIST_CONTROL, type OptionsListEmbeddableInput } from './options_list/types';
export {
type RangeSliderEmbeddableInput,
RANGE_SLIDER_CONTROL,
Expand Down
41 changes: 2 additions & 39 deletions src/plugins/controls/common/mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,5 @@
* Side Public License, v 1.
*/

import { getDefaultControlGroupInput } from '.';
import { ControlGroupInput } from './control_group/types';

export const mockControlGroupInput = (partial?: Partial<ControlGroupInput>): ControlGroupInput => ({
id: 'mocked_control_group',
...getDefaultControlGroupInput(),
...{
panels: {
control1: {
order: 0,
width: 'medium',
grow: true,
type: 'mockedOptionsList',
explicitInput: {
id: 'control1',
},
},
control2: {
order: 1,
width: 'large',
grow: true,
type: 'mockedRangeSlider',
explicitInput: {
id: 'control2',
},
},
control3: {
order: 2,
width: 'small',
grow: true,
type: 'mockedOptionsList',
explicitInput: {
id: 'control3',
},
},
},
},
...(partial ?? {}),
});
export * from './control_group/mocks';
export * from './options_list/mocks';
63 changes: 63 additions & 0 deletions src/plugins/controls/common/options_list/mocks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 { ReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public/redux_embeddables/types';
import { ControlOutput } from '../../public/types';
import {
OptionsListComponentState,
OptionsListEmbeddableInput,
OptionsListReduxState,
} from '../../public/options_list/types';
import { optionsListReducers } from '../../public/options_list/options_list_reducers';

const mockOptionsListComponentState = {
field: undefined,
totalCardinality: 0,
availableOptions: ['woof', 'bark', 'meow', 'quack', 'moo'],
invalidSelections: [],
validSelections: [],
searchString: '',
} as OptionsListComponentState;

const mockOptionsListEmbeddableInput = {
id: 'sample options list',
fieldName: 'sample field',
dataViewId: 'sample id',
selectedOptions: [],
runPastTimeout: false,
singleSelect: false,
} as OptionsListEmbeddableInput;

const mockOptionsListOutput = {
loading: false,
} as ControlOutput;

export const mockOptionsListContext = (
partialState?: Partial<OptionsListReduxState>
): ReduxEmbeddableContext<OptionsListReduxState, typeof optionsListReducers> => {
const mockReduxState = {
componentState: {
...mockOptionsListComponentState,
...partialState?.componentState,
},
explicitInput: {
...mockOptionsListEmbeddableInput,
...partialState?.explicitInput,
},
output: {
...mockOptionsListOutput,
...partialState?.output,
},
} as OptionsListReduxState;

return {
actions: {},
useEmbeddableDispatch: () => {},
useEmbeddableSelector: (selector: any) => selector(mockReduxState),
} as unknown as ReduxEmbeddableContext<OptionsListReduxState, typeof optionsListReducers>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import type { Filter, Query, BoolQuery, TimeRange } from '@kbn/es-query';
import { FieldSpec, DataView } from '@kbn/data-views-plugin/common';

import { DataControlInput } from '../../types';
import { DataControlInput } from '../types';

export const OPTIONS_LIST_CONTROL = 'optionsListControl';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { injectStorybookDataView } from '../services/storybook/data_views';
import { replaceOptionsListMethod } from '../services/storybook/options_list';
import { populateStorybookControlFactories } from './storybook_control_factories';
import { replaceValueSuggestionMethod } from '../services/storybook/unified_search';
import { OptionsListResponse, OptionsListRequest } from '../control_types/options_list/types';
import { OptionsListResponse, OptionsListRequest } from '../options_list/types';

export default {
title: 'Controls',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { OptionsListEmbeddableFactory } from '../control_types/options_list';
import { OptionsListEmbeddableFactory } from '../options_list';
import { RangeSliderEmbeddableFactory } from '../control_types/range_slider';
import { TimesliderEmbeddableFactory } from '../control_types/time_slider';
import { ControlsService } from '../services/controls';
Expand Down
1 change: 0 additions & 1 deletion src/plugins/controls/public/control_types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@
* Side Public License, v 1.
*/

export * from './options_list';
export * from './range_slider';
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ControlEmbeddable, DataControlField, IEditableControlFactory } from '..
import {
createOptionsListExtract,
createOptionsListInject,
} from '../../../common/control_types/options_list/options_list_persistable_state';
} from '../../../common/options_list/options_list_persistable_state';
import { TimeSliderControlEmbeddableInput } from '../../../common/control_types/time_slider/types';
import { TimeSliderStrings } from './time_slider_strings';

Expand Down
3 changes: 3 additions & 0 deletions src/plugins/controls/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export {
OptionsListEmbeddableFactory,
OptionsListEmbeddable,
type OptionsListEmbeddableInput,
} from './options_list';

export {
RangeSliderEmbeddableFactory,
RangeSliderEmbeddable,
type RangeSliderEmbeddableInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,22 @@
* Side Public License, v 1.
*/

import { EuiFilterButton, EuiFilterGroup, EuiPopover, useResizeObserver } from '@elastic/eui';
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import { Subject } from 'rxjs';
import classNames from 'classnames';
import { debounce, isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';

import { EuiFilterButton, EuiFilterGroup, EuiPopover, useResizeObserver } from '@elastic/eui';
import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public';

import { OptionsListStrings } from './options_list_strings';
import { optionsListReducers } from './options_list_reducers';
import { OptionsListPopover } from './options_list_popover_component';
import { OptionsListPopover } from './options_list_popover';
import { optionsListReducers } from '../options_list_reducers';
import { OptionsListReduxState } from '../types';

import './options_list.scss';
import { OptionsListReduxState } from './types';

export const OptionsListComponent = ({
typeaheadSubject,
}: {
typeaheadSubject: Subject<string>;
}) => {
export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Subject<string> }) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);

const resizeRef = useRef(null);
Expand Down Expand Up @@ -79,11 +76,11 @@ export const OptionsListComponent = ({
selectionDisplayNode: (
<>
{validSelections && (
<span>{validSelections?.join(OptionsListStrings.summary.getSeparator())}</span>
<span>{validSelections?.join(OptionsListStrings.control.getSeparator())}</span>
)}
{invalidSelections && (
<span className="optionsList__filterInvalid">
{invalidSelections.join(OptionsListStrings.summary.getSeparator())}
{invalidSelections.join(OptionsListStrings.control.getSeparator())}
</span>
)}
</>
Expand All @@ -106,7 +103,7 @@ export const OptionsListComponent = ({
numActiveFilters={validSelectionsCount}
hasActiveFilters={Boolean(validSelectionsCount)}
>
{hasSelections ? selectionDisplayNode : OptionsListStrings.summary.getPlaceholder()}
{hasSelections ? selectionDisplayNode : OptionsListStrings.control.getPlaceholder()}
</EuiFilterButton>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
*/

import React, { useState } from 'react';

import { EuiFormRow, EuiSwitch } from '@elastic/eui';

import { OptionsListEmbeddableInput } from './types';
import { OptionsListStrings } from './options_list_strings';
import { OptionsListEmbeddableInput } from '../types';
import { ControlEditorProps } from '../..';

interface OptionsListEditorState {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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 { ReactWrapper } from 'enzyme';

import { mountWithIntl } from '@kbn/test-jest-helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { EmbeddableReduxContext } from '@kbn/presentation-util-plugin/public/redux_embeddables/use_redux_embeddable_context';

import { OptionsListPopover, OptionsListPopoverProps } from './options_list_popover';
import {
OptionsListComponentState,
OptionsListEmbeddableInput,
OptionsListReduxState,
} from '../types';
import { ControlOutput } from '../..';
import { mockOptionsListContext } from '../../../common/mocks';

describe('Options list popover', () => {
const defaultProps = {
width: 500,
updateSearchString: jest.fn(),
};

interface MountOptions {
componentState: Partial<OptionsListComponentState>;
explicitInput: Partial<OptionsListEmbeddableInput>;
output: Partial<ControlOutput>;
popoverProps: Partial<OptionsListPopoverProps>;
}

function mountComponent(options?: Partial<MountOptions>) {
const compProps = { ...defaultProps, ...(options?.popoverProps ?? {}) };
const context = mockOptionsListContext({
componentState: options?.componentState ?? {},
explicitInput: options?.explicitInput ?? {},
output: options?.output ?? {},
} as Partial<OptionsListReduxState>);

return mountWithIntl(
<EmbeddableReduxContext.Provider value={context}>
<OptionsListPopover {...compProps} />
</EmbeddableReduxContext.Provider>
);
}

const clickShowOnlySelections = (popover: ReactWrapper) => {
const showOnlySelectedButton = findTestSubject(
popover,
'optionsList-control-show-only-selected'
);
showOnlySelectedButton.simulate('click');
};

test('available options list width responds to container size', () => {
let popover = mountComponent({ popoverProps: { width: 301 } });
let availableOptionsDiv = findTestSubject(popover, 'optionsList-control-available-options');
expect(availableOptionsDiv.getDOMNode().getAttribute('style')).toBe('width: 301px;');

// the div cannot be smaller than 301 pixels wide
popover = mountComponent({ popoverProps: { width: 300 } });
availableOptionsDiv = findTestSubject(popover, 'optionsList-control-available-options');
expect(availableOptionsDiv.getDOMNode().getAttribute('style')).toBe(null);
});

test('no available options', () => {
const popover = mountComponent({ componentState: { availableOptions: [] } });
const availableOptionsDiv = findTestSubject(popover, 'optionsList-control-available-options');
const noOptionsDiv = findTestSubject(
availableOptionsDiv,
'optionsList-control-noSelectionsMessage'
);
expect(noOptionsDiv.exists()).toBeTruthy();
});

test('display error message when the show only selected toggle is true but there are no selections', () => {
const popover = mountComponent();
clickShowOnlySelections(popover);
const availableOptionsDiv = findTestSubject(popover, 'optionsList-control-available-options');
const noSelectionsDiv = findTestSubject(
availableOptionsDiv,
'optionsList-control-selectionsEmptyMessage'
);
expect(noSelectionsDiv.exists()).toBeTruthy();
});

test('show only selected options', () => {
const selections = ['woof', 'bark'];
const popover = mountComponent({
explicitInput: { selectedOptions: selections },
});
clickShowOnlySelections(popover);
const availableOptionsDiv = findTestSubject(popover, 'optionsList-control-available-options');
availableOptionsDiv.children().forEach((child, i) => {
expect(child.text()).toBe(selections[i]);
});
});
});
Loading

0 comments on commit e0085c4

Please sign in to comment.