[controls] fix option list control making 2 requests on refresh (#219625)

Fixes https://github.com/elastic/kibana/issues/218663
This commit is contained in:
Nathan Reese 2025-04-29 16:56:52 -06:00 committed by GitHub
parent ae88fa8549
commit f13c764115
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 13 additions and 27 deletions

View file

@ -10,7 +10,7 @@
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { PublishesUnifiedSearch, PublishingSubject } from '@kbn/presentation-publishing'; import { PublishesUnifiedSearch, PublishingSubject } from '@kbn/presentation-publishing';
import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload'; import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload';
import { BehaviorSubject, debounceTime, map, merge, Observable, switchMap } from 'rxjs'; import { BehaviorSubject, debounceTime, map, merge, Observable, switchMap, tap } from 'rxjs';
import { ParentIgnoreSettings } from '../../../common'; import { ParentIgnoreSettings } from '../../../common';
export interface ControlGroupFetchContext { export interface ControlGroupFetchContext {
@ -23,7 +23,8 @@ export function controlGroupFetch$(
ignoreParentSettings$: PublishingSubject<ParentIgnoreSettings | undefined>, ignoreParentSettings$: PublishingSubject<ParentIgnoreSettings | undefined>,
parentApi: Partial<PublishesUnifiedSearch> & { parentApi: Partial<PublishesUnifiedSearch> & {
unifiedSearchFilters$?: PublishingSubject<Filter[] | undefined>; unifiedSearchFilters$?: PublishingSubject<Filter[] | undefined>;
} },
onReload?: () => void
): Observable<ControlGroupFetchContext> { ): Observable<ControlGroupFetchContext> {
return ignoreParentSettings$.pipe( return ignoreParentSettings$.pipe(
switchMap((parentIgnoreSettings) => { switchMap((parentIgnoreSettings) => {
@ -40,7 +41,7 @@ export function controlGroupFetch$(
observables.push(parentApi.timeRange$); observables.push(parentApi.timeRange$);
} }
if (apiPublishesReload(parentApi)) { if (apiPublishesReload(parentApi)) {
observables.push(parentApi.reload$); observables.push(onReload ? parentApi.reload$.pipe(tap(onReload)) : parentApi.reload$);
} }
return observables.length ? merge(...observables) : new BehaviorSubject(undefined); return observables.length ? merge(...observables) : new BehaviorSubject(undefined);
}), }),

View file

@ -135,7 +135,7 @@ export const getControlGroupEmbeddableFactory = () => {
disabledActionIds$, disabledActionIds$,
...unsavedChanges.api, ...unsavedChanges.api,
...selectionsManager.api, ...selectionsManager.api,
controlFetch$: (controlUuid: string) => controlFetch$: (controlUuid: string, onReload?: () => void) =>
controlFetch$( controlFetch$(
chaining$( chaining$(
controlUuid, controlUuid,
@ -143,7 +143,7 @@ export const getControlGroupEmbeddableFactory = () => {
controlsManager.controlsInOrder$, controlsManager.controlsInOrder$,
controlsManager.api.children$ controlsManager.api.children$
), ),
controlGroupFetch$(ignoreParentSettings$, parentApi ? parentApi : {}) controlGroupFetch$(ignoreParentSettings$, parentApi ? parentApi : {}, onReload)
), ),
ignoreParentSettings$, ignoreParentSettings$,
autoApplySelections$, autoApplySelections$,

View file

@ -65,7 +65,7 @@ export type ControlGroupApi = PresentationContainer &
labelPosition: PublishingSubject<ControlLabelPosition>; labelPosition: PublishingSubject<ControlLabelPosition>;
asyncResetUnsavedChanges: () => Promise<void>; asyncResetUnsavedChanges: () => Promise<void>;
controlFetch$: (controlUuid: string) => Observable<ControlFetchContext>; controlFetch$: (controlUuid: string, onReload?: () => void) => Observable<ControlFetchContext>;
openAddDataControlFlyout: (options?: { openAddDataControlFlyout: (options?: {
controlStateTransform?: ControlStateTransform; controlStateTransform?: ControlStateTransform;
onSave?: () => void; onSave?: () => void;

View file

@ -12,7 +12,6 @@ import {
combineLatest, combineLatest,
debounceTime, debounceTime,
Observable, Observable,
of,
startWith, startWith,
switchMap, switchMap,
tap, tap,
@ -20,7 +19,6 @@ import {
} from 'rxjs'; } from 'rxjs';
import { PublishingSubject } from '@kbn/presentation-publishing'; import { PublishingSubject } from '@kbn/presentation-publishing';
import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload';
import { OptionsListSuccessResponse } from '../../../../common/options_list/types'; import { OptionsListSuccessResponse } from '../../../../common/options_list/types';
import { isValidSearch } from '../../../../common/options_list/is_valid_search'; import { isValidSearch } from '../../../../common/options_list/is_valid_search';
import { OptionsListSelection } from '../../../../common/options_list/options_list_selections'; import { OptionsListSelection } from '../../../../common/options_list/options_list_selections';
@ -32,10 +30,10 @@ import { OptionsListComponentApi, OptionsListComponentState, OptionsListControlA
export function fetchAndValidate$({ export function fetchAndValidate$({
api, api,
stateManager, stateManager,
controlFetch$,
}: { }: {
api: Pick<OptionsListControlApi, 'dataViews$' | 'field$' | 'setBlockingError' | 'parentApi'> & api: Pick<OptionsListControlApi, 'dataViews$' | 'field$' | 'setBlockingError' | 'parentApi'> &
Pick<OptionsListComponentApi, 'loadMoreSubject'> & { Pick<OptionsListComponentApi, 'loadMoreSubject'> & {
controlFetch$: Observable<ControlFetchContext>;
loadingSuggestions$: BehaviorSubject<boolean>; loadingSuggestions$: BehaviorSubject<boolean>;
debouncedSearchString: Observable<string>; debouncedSearchString: Observable<string>;
}; };
@ -44,6 +42,7 @@ export function fetchAndValidate$({
> & { > & {
selectedOptions: PublishingSubject<OptionsListSelection[] | undefined>; selectedOptions: PublishingSubject<OptionsListSelection[] | undefined>;
}; };
controlFetch$: (onReload: () => void) => Observable<ControlFetchContext>;
}): Observable<OptionsListSuccessResponse | { error: Error }> { }): Observable<OptionsListSuccessResponse | { error: Error }> {
const requestCache = new OptionsListFetchCache(); const requestCache = new OptionsListFetchCache();
let abortController: AbortController | undefined; let abortController: AbortController | undefined;
@ -51,7 +50,7 @@ export function fetchAndValidate$({
return combineLatest([ return combineLatest([
api.dataViews$, api.dataViews$,
api.field$, api.field$,
api.controlFetch$, controlFetch$(requestCache.clearCache),
api.parentApi.allowExpensiveQueries$, api.parentApi.allowExpensiveQueries$,
api.parentApi.ignoreParentSettings$, api.parentApi.ignoreParentSettings$,
api.debouncedSearchString, api.debouncedSearchString,
@ -62,12 +61,6 @@ export function fetchAndValidate$({
startWith(null), // start with null so that `combineLatest` subscription fires startWith(null), // start with null so that `combineLatest` subscription fires
debounceTime(100) // debounce load more so "loading" state briefly shows debounceTime(100) // debounce load more so "loading" state briefly shows
), ),
apiPublishesReload(api.parentApi)
? api.parentApi.reload$.pipe(
tap(() => requestCache.clearCache()),
startWith(undefined)
)
: of(undefined),
]).pipe( ]).pipe(
tap(() => { tap(() => {
// abort any in progress requests // abort any in progress requests

View file

@ -185,9 +185,9 @@ export const getOptionsListControlFactory = (): DataControlFactory<
loadingSuggestions$, loadingSuggestions$,
debouncedSearchString, debouncedSearchString,
parentApi: controlGroupApi, parentApi: controlGroupApi,
controlFetch$: controlGroupApi.controlFetch$(uuid),
}, },
stateManager, stateManager,
controlFetch$: (onReload: () => void) => controlGroupApi.controlFetch$(uuid, onReload),
}).subscribe((result) => { }).subscribe((result) => {
// if there was an error during fetch, set blocking error and return early // if there was an error during fetch, set blocking error and return early
if (Object.hasOwn(result, 'error')) { if (Object.hasOwn(result, 'error')) {

View file

@ -11,8 +11,7 @@ import type { estypes } from '@elastic/elasticsearch';
import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { PublishesDataViews, PublishingSubject } from '@kbn/presentation-publishing'; import { PublishesDataViews, PublishingSubject } from '@kbn/presentation-publishing';
import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload'; import { Observable, combineLatest, lastValueFrom, switchMap, tap } from 'rxjs';
import { Observable, combineLatest, lastValueFrom, of, startWith, switchMap, tap } from 'rxjs';
import { dataService } from '../../../services/kibana_services'; import { dataService } from '../../../services/kibana_services';
import { ControlFetchContext } from '../../../control_group/control_fetch'; import { ControlFetchContext } from '../../../control_group/control_fetch';
import { ControlGroupApi } from '../../../control_group/types'; import { ControlGroupApi } from '../../../control_group/types';
@ -31,14 +30,7 @@ export function minMax$({
setIsLoading: (isLoading: boolean) => void; setIsLoading: (isLoading: boolean) => void;
}) { }) {
let prevRequestAbortController: AbortController | undefined; let prevRequestAbortController: AbortController | undefined;
return combineLatest([ return combineLatest([controlFetch$, dataViews$, fieldName$]).pipe(
controlFetch$,
dataViews$,
fieldName$,
apiPublishesReload(controlGroupApi)
? controlGroupApi.reload$.pipe(startWith(undefined))
: of(undefined),
]).pipe(
tap(() => { tap(() => {
if (prevRequestAbortController) { if (prevRequestAbortController) {
prevRequestAbortController.abort(); prevRequestAbortController.abort();