From 18aff792bfb19a0388911b2527a01d8c9eacc417 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Tue, 24 Jan 2023 18:11:34 +0100 Subject: [PATCH] [feat][Kibana Presentation] Options List new feature (#149121) ## Background Security solution recently started using controls plugin to provide users some extra filtering capability with help of options list control. During this implementation, there was some feedback was given from design team + we were facing some minor issues because of the caches. Below section gives the list of changes and the reasoning behind each change. All of these changes were discussed with @ThomThomson ## Summary This PR introduces 3 new functionalities for optionsList embeddables. 1. Cache invalidation option when reloading an optionsList - In security solution we have transactional alerting system, where user frequently update alerts data - We need the latest data and 1 minute cache of optionsList was preventing us from doing so. - This change adds the capability to clear the cache from an embeddable. 3. option to hideSearch Panel - As a client of control plugin, we look some control over what panels are visible and what are not. 5. Option to add custom placeholder for optionsList - Out product team felt that default placeholder for optionsList `Any` may be confusing for the user and hence I have added the option for clients to provide a placeholder. ### Checklist Delete any items that are not applicable to this PR. - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials ### 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) --- src/plugins/controls/common/options_list/types.ts | 2 ++ .../public/options_list/components/options_list_control.tsx | 4 +++- .../options_list/components/options_list_popover.test.tsx | 2 +- .../public/options_list/components/options_list_popover.tsx | 5 +++-- .../options_list/embeddable/options_list_embeddable.tsx | 2 ++ .../public/services/options_list/options_list.story.ts | 3 +++ .../public/services/options_list/options_list_service.ts | 4 ++++ src/plugins/controls/public/services/options_list/types.ts | 2 ++ 8 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/plugins/controls/common/options_list/types.ts b/src/plugins/controls/common/options_list/types.ts index 8835c7f5767f6..1c9555bdc8d90 100644 --- a/src/plugins/controls/common/options_list/types.ts +++ b/src/plugins/controls/common/options_list/types.ts @@ -23,7 +23,9 @@ export interface OptionsListEmbeddableInput extends DataControlInput { hideExclude?: boolean; hideExists?: boolean; hideSort?: boolean; + hideActionBar?: boolean; exclude?: boolean; + placeholder?: string; } export type OptionsListField = FieldSpec & { diff --git a/src/plugins/controls/public/options_list/components/options_list_control.tsx b/src/plugins/controls/public/options_list/components/options_list_control.tsx index 3035eada20caf..98f545718efc6 100644 --- a/src/plugins/controls/public/options_list/components/options_list_control.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_control.tsx @@ -47,6 +47,8 @@ export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Sub const exclude = select((state) => state.explicitInput.exclude); const id = select((state) => state.explicitInput.id); + const placeholder = select((state) => state.explicitInput.placeholder); + const loading = select((state) => state.output.loading); // debounce loading state so loading doesn't flash when user types @@ -128,7 +130,7 @@ export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Sub > {hasSelections || existsSelected ? selectionDisplayNode - : OptionsListStrings.control.getPlaceholder()} + : placeholder ?? OptionsListStrings.control.getPlaceholder()} ); diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx index acb5d24d80659..a8504aba372c8 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx @@ -58,7 +58,7 @@ describe('Options list popover', () => { test('available options list width responds to container size', async () => { let popover = await mountComponent({ popoverProps: { width: 301 } }); let popoverDiv = findTestSubject(popover, 'optionsList-control-popover'); - expect(popoverDiv.getDOMNode().getAttribute('style')).toBe('width: 301px;'); + expect(popoverDiv.getDOMNode().getAttribute('style')).toBe('width: 301px; min-width: 300px;'); // the div cannot be smaller than 301 pixels wide popover = await mountComponent({ popoverProps: { width: 300 } }); diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.tsx index 70353524068cd..b5562c43f3be2 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover.tsx @@ -43,6 +43,7 @@ export const OptionsListPopover = ({ const field = select((state) => state.componentState.field); const hideExclude = select((state) => state.explicitInput.hideExclude); + const hideActionBar = select((state) => state.explicitInput.hideActionBar); const fieldName = select((state) => state.explicitInput.fieldName); const title = select((state) => state.explicitInput.title); const id = select((state) => state.explicitInput.id); @@ -52,12 +53,12 @@ export const OptionsListPopover = ({ return (
300 ? width : undefined }} + style={{ width, minWidth: 300 }} data-test-subj={`optionsList-control-popover`} aria-label={OptionsListStrings.popover.getAriaLabel(fieldName)} > {title} - {field?.type !== 'boolean' && ( + {field?.type !== 'boolean' && !hideActionBar && ( { + // clear cache when reload is requested + this.optionsListService.clearOptionsListCache(); this.runOptionsListQuery(); }; diff --git a/src/plugins/controls/public/services/options_list/options_list.story.ts b/src/plugins/controls/public/services/options_list/options_list.story.ts index 62686feee7495..c641e9ee8834a 100644 --- a/src/plugins/controls/public/services/options_list/options_list.story.ts +++ b/src/plugins/controls/public/services/options_list/options_list.story.ts @@ -26,6 +26,8 @@ let optionsListRequestMethod = async (request: OptionsListRequest, abortSignal: ) ); +const clearOptionsListCacheMock = () => {}; + export const replaceOptionsListMethod = ( newMethod: (request: OptionsListRequest, abortSignal: AbortSignal) => Promise ) => (optionsListRequestMethod = newMethod); @@ -33,5 +35,6 @@ export const replaceOptionsListMethod = ( export const optionsListServiceFactory: OptionsListServiceFactory = () => { return { runOptionsListRequest: optionsListRequestMethod, + clearOptionsListCache: clearOptionsListCacheMock, }; }; diff --git a/src/plugins/controls/public/services/options_list/options_list_service.ts b/src/plugins/controls/public/services/options_list/options_list_service.ts index ab8e67666140b..888d2e2efb837 100644 --- a/src/plugins/controls/public/services/options_list/options_list_service.ts +++ b/src/plugins/controls/public/services/options_list/options_list_service.ts @@ -104,6 +104,10 @@ class OptionsListService implements ControlsOptionsListService { return { rejected: true } as OptionsListResponse; } }; + + public clearOptionsListCache = () => { + this.cachedOptionsListRequest.cache = new memoize.Cache(); + }; } export interface OptionsListServiceRequiredServices { diff --git a/src/plugins/controls/public/services/options_list/types.ts b/src/plugins/controls/public/services/options_list/types.ts index d493207df591c..569042b136419 100644 --- a/src/plugins/controls/public/services/options_list/types.ts +++ b/src/plugins/controls/public/services/options_list/types.ts @@ -13,4 +13,6 @@ export interface ControlsOptionsListService { request: OptionsListRequest, abortSignal: AbortSignal ) => Promise; + + clearOptionsListCache: () => void; }