Skip to content

Commit

Permalink
[embeddable rebuild] integrate react control group embeddable into da…
Browse files Browse the repository at this point in the history
…shboard container - reloaded (#192221)

PR replaces legacy embeddable control group implementation with react
control group implementation in DashboardContainer.

#### background
Work originally done in #190273.
#190273 was reverted by
#191993 because of dashboard
performance degradation. It was determined that degradation was because
new react embeddable controls fixed a regression where dashboard panels
are loading before control filters are created. This regression was
introduced by #187509.

The work around is that this PR keeps the currently broken behavior in
main and loads panels before control filters are ready. The thinking is
that the migration would replace like for like and not introduce any
performance changes. Then, at a later time, the regression could be
resolved.

#### reviewing
These are the same changes from
#190273 minus some work to
introduce a current regression in main. A full re-review is not needed.

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Hannah Mudge <hannah.wright@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 10, 2024
1 parent 96739aa commit 9dfad5b
Show file tree
Hide file tree
Showing 64 changed files with 1,197 additions and 1,081 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ const initialSerializedControlGroupState = {
} as object,
references: [
{
name: `controlGroup_${rangeSliderControlId}:${RANGE_SLIDER_CONTROL}DataView`,
name: `controlGroup_${rangeSliderControlId}:rangeSliderDataView`,
type: 'index-pattern',
id: WEB_LOGS_DATA_VIEW_ID,
},
{
name: `controlGroup_${optionsListId}:${OPTIONS_LIST_CONTROL}DataView`,
name: `controlGroup_${optionsListId}:optionsListDataView`,
type: 'index-pattern',
id: WEB_LOGS_DATA_VIEW_ID,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import React, { useEffect, useState } from 'react';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { controlGroupInputBuilder } from '@kbn/controls-plugin/public';
import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common';
import { controlGroupStateBuilder } from '@kbn/controls-plugin/public';
import {
AwaitingDashboardAPI,
DashboardRenderer,
Expand Down Expand Up @@ -64,16 +63,15 @@ export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView
<EuiPanel hasBorder={true}>
<DashboardRenderer
getCreationOptions={async (): Promise<DashboardCreationOptions> => {
const builder = controlGroupInputBuilder;
const controlGroupInput = getDefaultControlGroupInput();
await builder.addDataControlFromField(controlGroupInput, {
const controlGroupState = {};
await controlGroupStateBuilder.addDataControlFromField(controlGroupState, {
dataViewId: dataView.id ?? '',
title: 'Destintion country',
fieldName: 'geo.dest',
width: 'medium',
grow: false,
});
await builder.addDataControlFromField(controlGroupInput, {
await controlGroupStateBuilder.addDataControlFromField(controlGroupState, {
dataViewId: dataView.id ?? '',
fieldName: 'bytes',
width: 'medium',
Expand All @@ -86,7 +84,7 @@ export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView
getInitialInput: () => ({
timeRange: { from: 'now-30d', to: 'now' },
viewMode: ViewMode.VIEW,
controlGroupInput,
controlGroupState,
}),
};
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import deepEqual from 'fast-deep-equal';
import { SerializableRecord } from '@kbn/utility-types';

import { v4 } from 'uuid';
import { pick, omit, xor } from 'lodash';

import {
Expand All @@ -24,7 +23,6 @@ import {
} from './control_group_panel_diff_system';
import { ControlGroupInput } from '..';
import {
ControlsPanels,
PersistableControlGroupInput,
persistableControlGroupInputKeys,
RawControlGroupAttributes,
Expand Down Expand Up @@ -104,32 +102,6 @@ const getPanelsAreEqual = (
return true;
};

export const controlGroupInputToRawControlGroupAttributes = (
controlGroupInput: Omit<ControlGroupInput, 'id'>
): RawControlGroupAttributes => {
return {
controlStyle: controlGroupInput.controlStyle,
chainingSystem: controlGroupInput.chainingSystem,
showApplySelections: controlGroupInput.showApplySelections,
panelsJSON: JSON.stringify(controlGroupInput.panels),
ignoreParentSettingsJSON: JSON.stringify(controlGroupInput.ignoreParentSettings),
};
};

export const generateNewControlIds = (controlGroupInput?: PersistableControlGroupInput) => {
if (!controlGroupInput?.panels) return;

const newPanelsMap: ControlsPanels = {};
for (const panel of Object.values(controlGroupInput.panels)) {
const newId = v4();
newPanelsMap[newId] = {
...panel,
explicitInput: { ...panel.explicitInput, id: newId },
};
}
return { ...controlGroupInput, panels: newPanelsMap };
};

export const rawControlGroupAttributesToControlGroupInput = (
rawControlGroupAttributes: RawControlGroupAttributes
): PersistableControlGroupInput | undefined => {
Expand Down
2 changes: 0 additions & 2 deletions src/plugins/controls/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ export {
persistableControlGroupInputKeys,
} from './control_group/types';
export {
controlGroupInputToRawControlGroupAttributes,
rawControlGroupAttributesToControlGroupInput,
rawControlGroupAttributesToSerializable,
serializableToRawControlGroupAttributes,
getDefaultControlGroupPersistableInput,
persistableControlGroupInputIsEqual,
getDefaultControlGroupInput,
generateNewControlIds,
} from './control_group/control_group_persistence';

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export function ControlGroup({
paddingSize="none"
color={draggingId ? 'success' : 'transparent'}
className="controlsWrapper"
data-test-subj="controls-group-wrapper"
>
<EuiFlexGroup
gutterSize="s"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ import { apiPublishesAsyncFilters } from '../controls/data_controls/publishes_as

export type ControlGroupComparatorState = Pick<
ControlGroupRuntimeState,
| 'autoApplySelections'
| 'chainingSystem'
| 'ignoreParentSettings'
| 'initialChildControlState'
| 'labelPosition'
'autoApplySelections' | 'chainingSystem' | 'ignoreParentSettings' | 'labelPosition'
> & {
controlsInOrder: ControlsInOrder;
};
Expand All @@ -39,6 +35,7 @@ export function initializeControlGroupUnsavedChanges(
children$: PresentationContainer['children$'],
comparators: StateComparators<ControlGroupComparatorState>,
snapshotControlsRuntimeState: () => ControlPanelsState,
resetControlsUnsavedChanges: () => void,
parentApi: unknown,
lastSavedRuntimeState: ControlGroupRuntimeState
) {
Expand All @@ -48,7 +45,6 @@ export function initializeControlGroupUnsavedChanges(
chainingSystem: lastSavedRuntimeState.chainingSystem,
controlsInOrder: getControlsInOrder(lastSavedRuntimeState.initialChildControlState),
ignoreParentSettings: lastSavedRuntimeState.ignoreParentSettings,
initialChildControlState: lastSavedRuntimeState.initialChildControlState,
labelPosition: lastSavedRuntimeState.labelPosition,
},
parentApi,
Expand All @@ -73,6 +69,7 @@ export function initializeControlGroupUnsavedChanges(
),
asyncResetUnsavedChanges: async () => {
controlGroupUnsavedChanges.api.resetUnsavedChanges();
resetControlsUnsavedChanges();

const filtersReadyPromises: Array<Promise<void>> = [];
Object.values(children$.value).forEach((controlApi) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,19 @@ import { chaining$, controlFetch$, controlGroupFetch$ } from './control_fetch';
import { initControlsManager } from './init_controls_manager';
import { openEditControlGroupFlyout } from './open_edit_control_group_flyout';
import { deserializeControlGroup } from './serialization_utils';
import { ControlGroupApi, ControlGroupRuntimeState, ControlGroupSerializedState } from './types';
import {
ControlGroupApi,
ControlGroupRuntimeState,
ControlGroupSerializedState,
ControlPanelsState,
} from './types';
import { ControlGroup } from './components/control_group';
import { initSelectionsManager } from './selections_manager';
import { initializeControlGroupUnsavedChanges } from './control_group_unsaved_changes_api';
import { openDataControlEditor } from '../controls/data_controls/open_data_control_editor';

const DEFAULT_CHAINING_SYSTEM = 'HIERARCHICAL';

export const getControlGroupEmbeddableFactory = (services: {
core: CoreStart;
dataViews: DataViewsPublicPluginStart;
Expand All @@ -61,27 +68,29 @@ export const getControlGroupEmbeddableFactory = (services: {
lastSavedRuntimeState
) => {
const {
initialChildControlState,
labelPosition: initialLabelPosition,
chainingSystem,
autoApplySelections,
ignoreParentSettings,
} = initialRuntimeState;

const autoApplySelections$ = new BehaviorSubject<boolean>(autoApplySelections);
const parentDataViewId = apiPublishesDataViews(parentApi)
? parentApi.dataViews.value?.[0]?.id
: undefined;
const defaultDataViewId = await services.dataViews.getDefaultId();
const lastSavedControlsState$ = new BehaviorSubject<ControlPanelsState>(
lastSavedRuntimeState.initialChildControlState
);
const controlsManager = initControlsManager(
initialChildControlState,
parentDataViewId ?? (await services.dataViews.getDefaultId())
initialRuntimeState.initialChildControlState,
lastSavedControlsState$
);
const selectionsManager = initSelectionsManager({
...controlsManager.api,
autoApplySelections$,
});
const dataViews = new BehaviorSubject<DataView[] | undefined>(undefined);
const chainingSystem$ = new BehaviorSubject<ControlGroupChainingSystem>(chainingSystem);
const chainingSystem$ = new BehaviorSubject<ControlGroupChainingSystem>(
chainingSystem ?? DEFAULT_CHAINING_SYSTEM
);
const ignoreParentSettings$ = new BehaviorSubject<ParentIgnoreSettings | undefined>(
ignoreParentSettings
);
Expand All @@ -105,6 +114,7 @@ export const getControlGroupEmbeddableFactory = (services: {
chainingSystem: [
chainingSystem$,
(next: ControlGroupChainingSystem) => chainingSystem$.next(next),
(a, b) => (a ?? DEFAULT_CHAINING_SYSTEM) === (b ?? DEFAULT_CHAINING_SYSTEM),
],
ignoreParentSettings: [
ignoreParentSettings$,
Expand All @@ -114,6 +124,7 @@ export const getControlGroupEmbeddableFactory = (services: {
labelPosition: [labelPosition$, (next: ControlStyle) => labelPosition$.next(next)],
},
controlsManager.snapshotControlsRuntimeState,
controlsManager.resetControlsUnsavedChanges,
parentApi,
lastSavedRuntimeState
);
Expand Down Expand Up @@ -160,20 +171,28 @@ export const getControlGroupEmbeddableFactory = (services: {
i18n.translate('controls.controlGroup.displayName', {
defaultMessage: 'Controls',
}),
openAddDataControlFlyout: (settings) => {
const { controlInputTransform } = settings ?? {
controlInputTransform: (state) => state,
};
openAddDataControlFlyout: (options) => {
const parentDataViewId = apiPublishesDataViews(parentApi)
? parentApi.dataViews.value?.[0]?.id
: undefined;
const newControlState = controlsManager.getNewControlState();
openDataControlEditor({
initialState: controlsManager.getNewControlState(),
initialState: {
...newControlState,
dataViewId:
newControlState.dataViewId ?? parentDataViewId ?? defaultDataViewId ?? undefined,
},
onSave: ({ type: controlType, state: initialState }) => {
controlsManager.api.addNewPanel({
panelType: controlType,
initialState: controlInputTransform!(
initialState as Partial<ControlGroupSerializedState>,
controlType
),
initialState: options?.controlInputTransform
? options.controlInputTransform(
initialState as Partial<ControlGroupSerializedState>,
controlType
)
: initialState,
});
options?.onSave?.();
},
controlGroupApi: api,
services,
Expand Down Expand Up @@ -208,21 +227,19 @@ export const getControlGroupEmbeddableFactory = (services: {
dataViews.next(newDataViews)
);

/** Fetch the allowExpensiveQuries setting for the children to use if necessary */
try {
const { allowExpensiveQueries } = await services.core.http.get<{
allowExpensiveQueries: boolean;
// TODO: Rename this route as part of https://github.com/elastic/kibana/issues/174961
}>('/internal/controls/optionsList/getExpensiveQueriesSetting', {
version: '1',
});
if (!allowExpensiveQueries) {
// only set if this returns false, since it defaults to true
allowExpensiveQueries$.next(allowExpensiveQueries);
}
} catch {
// do nothing - default to true on error (which it was initialized to)
}
const saveNotificationSubscription = apiHasSaveNotification(parentApi)
? parentApi.saveNotification$.subscribe(() => {
lastSavedControlsState$.next(controlsManager.snapshotControlsRuntimeState());

if (
typeof autoApplySelections$.value === 'boolean' &&
!autoApplySelections$.value &&
selectionsManager.hasUnappliedSelections$.value
) {
selectionsManager.applySelections();
}
})
: undefined;

return {
api,
Expand All @@ -233,9 +250,29 @@ export const getControlGroupEmbeddableFactory = (services: {
);

useEffect(() => {
/** Fetch the allowExpensiveQuries setting for the children to use if necessary */
const fetchAllowExpensiveQueries = async () => {
try {
const { allowExpensiveQueries } = await services.core.http.get<{
allowExpensiveQueries: boolean;
// TODO: Rename this route as part of https://github.com/elastic/kibana/issues/174961
}>('/internal/controls/optionsList/getExpensiveQueriesSetting', {
version: '1',
});
if (!allowExpensiveQueries) {
// only set if this returns false, since it defaults to true
allowExpensiveQueries$.next(allowExpensiveQueries);
}
} catch {
// do nothing - default to true on error (which it was initialized to)
}
};
fetchAllowExpensiveQueries(); // no need to await - don't want to block anything waiting for this

return () => {
selectionsManager.cleanup();
childrenDataViewsSubscription.unsubscribe();
saveNotificationSubscription?.unsubscribe();
};
}, []);

Expand Down
Loading

0 comments on commit 9dfad5b

Please sign in to comment.