Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const initializeUnsavedChanges = <SerializedStateType extends object = ob
anyStateChange$: Observable<void>;
serializeState: () => SerializedPanelState<SerializedStateType>;
getComparators: () => StateComparators<SerializedStateType>;
onReset: (lastSavedState?: SerializedPanelState<SerializedStateType>) => MaybePromise<void>;
onReset: (lastSavedPanelState?: SerializedPanelState<SerializedStateType>) => MaybePromise<void>;
}): PublishesUnsavedChanges => {
if (!apiHasLastSavedChildState<SerializedStateType>(parentApi)) {
return {
Expand Down
19 changes: 6 additions & 13 deletions x-pack/platform/plugins/shared/lens/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,21 +399,14 @@ export class LensPlugin {
// Let Dashboard know about the Lens panel type
embeddable.registerAddFromLibraryType<LensSavedObjectAttributes>({
onAdd: async (container, savedObject) => {
const [services, { deserializeState }] = await Promise.all([
getStartServicesForEmbeddable(),
import('./async_services'),
]);
// deserialize the saved object from visualize library
// this make sure to fit into the new embeddable model, where the following build()
// function expects a fully loaded runtime state
const state = await deserializeState(
services,
{ savedObjectId: savedObject.id },
savedObject.references
);
container.addNewPanel({
panelType: LENS_EMBEDDABLE_TYPE,
initialState: state,
serializedState: {
rawState: {
savedObjectId: savedObject.id,
},
references: savedObject.references
},
});
},
savedObjectType: LENS_EMBEDDABLE_TYPE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import { isObject } from 'lodash';
import { createMockDatasource, defaultDoc } from '../mocks';
import { ESQLVariableType, type ESQLControlVariable } from '@kbn/esql-types';
import * as Logger from './logger';
import { buildObservableVariable } from './helper';

jest.mock('@kbn/interpreter', () => ({
toExpression: jest.fn().mockReturnValue('expression'),
Expand Down Expand Up @@ -132,7 +131,7 @@ async function expectRerenderOnDataLoader(
const getState = jest.fn(() => runtimeState);
const internalApi = getLensInternalApiMock({
...internalApiOverrides,
attributes$: buildObservableVariable(runtimeState.attributes)[0],
attributes$: new BehaviorSubject(runtimeState.attributes),
});
const services = {
...makeEmbeddableServices(new BehaviorSubject<string>(''), undefined, {
Expand Down Expand Up @@ -567,7 +566,7 @@ describe('Data Loader', () => {
},
{
internalApiOverrides: {
esqlVariables$: buildObservableVariable<ESQLControlVariable[]>(variables)[0],
esqlVariables$: new BehaviorSubject<ESQLControlVariable[]>(variables),
},
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { buildUserMessagesHelpers } from './user_messages/api';
import { getLogError } from './expressions/telemetry';
import type { SharingSavedObjectProps, UserMessagesDisplayLocationId } from '../types';
import { apiHasLensComponentCallbacks } from './type_guards';
import { getRenderMode, getParentContext, buildObservableVariable } from './helper';
import { getRenderMode, getParentContext } from './helper';
import { addLog } from './logger';
import { getUsedDataViews } from './expressions/update_data_views';
import { getMergedSearchContext } from './expressions/merged_search_context';
Expand Down Expand Up @@ -119,7 +119,7 @@ export function loadEmbeddableData(
}
};

const [controlESQLVariables$] = buildObservableVariable<ESQLControlVariable[]>([]);
const controlESQLVariables$ = new BehaviorSubject<ESQLControlVariable[]>([]);

async function reload(
// make reload easier to debug
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
} from '@kbn/presentation-publishing';
import { isObject } from 'lodash';
import { BehaviorSubject } from 'rxjs';
import fastIsEqual from 'fast-deep-equal';
import { isOfAggregateQueryType } from '@kbn/es-query';
import { RenderMode } from '@kbn/expressions-plugin/common';
import { SavedObjectReference } from '@kbn/core/types';
Expand Down Expand Up @@ -99,39 +98,6 @@ export async function deserializeState(
return newState;
}

export function emptySerializer() {
return {};
}

export type ComparatorType<T extends unknown> = [
BehaviorSubject<T>,
(newValue: T) => void,
(a: T, b: T) => boolean
];

export function makeComparator<T extends unknown>(
observable: BehaviorSubject<T>
): ComparatorType<T> {
return [observable, (newValue: T) => observable.next(newValue), fastIsEqual];
}

/**
* Helper function to either extract an observable from an API or create a new one
* with a default value to start with.
* Note that extracting from the API will make subscription emit if the value changes upstream
* as it keeps the original reference without cloning.
* @returns the observable and a comparator to use for detecting "unsaved changes" on it
*/
export function buildObservableVariable<T extends unknown>(
variable: T | PublishingSubject<T>
): [BehaviorSubject<T>, ComparatorType<T>] {
if (variable instanceof BehaviorSubject) {
return [variable, makeComparator(variable)];
}
const variable$ = new BehaviorSubject<T>(variable as T);
return [variable$, makeComparator(variable$)];
}

export function isTextBasedLanguage(state: LensRuntimeState) {
return isOfAggregateQueryType(state.attributes?.state.query);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
PublishingSubject,
StateComparators,
apiPublishesUnifiedSearch,
getUnchangingComparator,
} from '@kbn/presentation-publishing';
import { HasDynamicActions } from '@kbn/embeddable-enhanced-plugin/public';
import { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public/plugin';
Expand All @@ -31,16 +30,18 @@ import { TableInspectorAdapter } from '../../editor_frame_service/types';

import { Datasource, IndexPatternMap } from '../../types';
import { getMergedSearchContext } from '../expressions/merged_search_context';
import { buildObservableVariable, isTextBasedLanguage } from '../helper';
import { isTextBasedLanguage } from '../helper';
import type {
GetStateType,
LensEmbeddableStartServices,
LensInternalApi,
LensRuntimeState,
LensSerializedState,
ViewInDiscoverCallbacks,
ViewUnderlyingDataArgs,
} from '../types';
import { getActiveDatasourceIdFromDoc, getActiveVisualizationIdFromDoc } from '../../utils';
import { BehaviorSubject, Observable } from 'rxjs';

function getViewUnderlyingDataArgs({
activeDatasource,
Expand Down Expand Up @@ -212,7 +213,7 @@ function createViewUnderlyingDataApis(
): ViewInDiscoverCallbacks {
let viewUnderlyingDataArgs: undefined | ViewUnderlyingDataArgs;

const [canViewUnderlyingData$] = buildObservableVariable<boolean>(false);
const canViewUnderlyingData$ = new BehaviorSubject<boolean>(false);

return {
canViewUnderlyingData$,
Expand Down Expand Up @@ -247,20 +248,22 @@ export function initializeActionApi(
services: LensEmbeddableStartServices
): {
api: ViewInDiscoverCallbacks & HasDynamicActions;
comparators: StateComparators<DynamicActionsSerializedState>;
serialize: () => {};
anyStateChange$: Observable<void>;
getComparators: () => StateComparators<DynamicActionsSerializedState>;
getLatestState: () => DynamicActionsSerializedState;
cleanup: () => void;
reinitializeState: (lastSaved?: LensSerializedState) => void;
} {
const dynamicActionsApi = services.embeddableEnhanced?.initializeReactEmbeddableDynamicActions(
const dynamicActionsManger = services.embeddableEnhanced?.initializeEmbeddableDynamicActions(
uuid,
() => title$.getValue(),
initialState
);
const maybeStopDynamicActions = dynamicActionsApi?.startDynamicActions();
const maybeStopDynamicActions = dynamicActionsManger?.startDynamicActions();

return {
api: {
...(isTextBasedLanguage(initialState) ? {} : dynamicActionsApi?.dynamicActionsApi ?? {}),
...(isTextBasedLanguage(initialState) ? {} : dynamicActionsManger?.api ?? {}),
...createViewUnderlyingDataApis(
getLatestState,
internalApi,
Expand All @@ -269,14 +272,18 @@ export function initializeActionApi(
services
),
},
comparators: {
...(dynamicActionsApi?.dynamicActionsComparator ?? {
enhancements: getUnchangingComparator(),
anyStateChange$: dynamicActionsManger?.anyStateChange$ ?? new BehaviorSubject(undefined),
getComparators: () => ({
...(dynamicActionsManger?.comparators ?? {
enhancements: 'skip',
}),
},
serialize: () => dynamicActionsApi?.serializeDynamicActions() ?? {},
}),
getLatestState: () => dynamicActionsManger?.getLatestState ?? {},
cleanup: () => {
maybeStopDynamicActions?.stopDynamicActions();
},
reinitializeState: (lastSaved?: LensSerializedState) => {
dynamicActionsManger?.reinitializeState(lastSaved ?? {});
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import {
PublishesWritableDescription,
SerializedTitles,
StateComparators,
getUnchangingComparator,
initializeTitleManager,
titleComparators,
} from '@kbn/presentation-publishing';
import { apiIsPresentationContainer, apiPublishesSettings } from '@kbn/presentation-containers';
import { buildObservableVariable, isTextBasedLanguage } from '../helper';
import { BehaviorSubject, Observable, map, merge } from 'rxjs';
import { isTextBasedLanguage } from '../helper';
import type {
LensComponentProps,
LensPanelProps,
Expand All @@ -35,17 +36,33 @@ import { StateManagementConfig } from './initialize_state_management';
// Convenience type for the serialized props of this initializer
type SerializedProps = SerializedTitles & LensPanelProps & LensOverrides & LensSharedProps;

export const dashboardServicesComparators: StateComparators<SerializedProps> = {
...titleComparators,
disableTriggers: 'referenceEquality',
overrides: 'referenceEquality',
id: 'skip',
palette: 'skip',
renderMode: 'skip',
syncColors: 'skip',
syncCursor: 'skip',
syncTooltips: 'skip',
executionContext: 'skip',
noPadding: 'skip',
viewMode: 'skip',
style: 'skip',
className: 'skip',
forceDSL: 'skip',
};

export interface DashboardServicesConfig {
api: PublishesWritableTitle &
PublishesWritableDescription &
HasLibraryTransforms<LensSerializedState, LensSerializedState> &
Pick<LensApi, 'parentApi'> &
Pick<IntegrationCallbacks, 'updateOverrides' | 'getTriggerCompatibleActions'>;
serialize: () => SerializedProps;
comparators: StateComparators<
SerializedProps & Pick<LensApi, 'parentApi'> & { isNewPanel?: boolean }
>;
cleanup: () => void;
anyStateChange$: Observable<void>;
getLatestState: () => SerializedProps;
reinitializeState: (lastSaved?: LensSerializedState) => void;
}

/**
Expand All @@ -62,22 +79,14 @@ export function initializeDashboardServices(
): DashboardServicesConfig {
// For some legacy reason the title and description default value is picked differently
// ( based on existing FTR tests ).
const [defaultTitle$] = buildObservableVariable<string | undefined>(
const defaultTitle$ = new BehaviorSubject<string | undefined>(
initialState.title || internalApi.attributes$.getValue().title
);
const [defaultDescription$] = buildObservableVariable<string | undefined>(
const defaultDescription$ = new BehaviorSubject<string | undefined>(
initialState.savedObjectId
? internalApi.attributes$.getValue().description || initialState.description
: initialState.description
);
// The observable references here are the same to the internalApi,
// the buildObservableVariable re-uses the same observable when detected but it builds the right comparator
const [overrides$, overridesComparator] = buildObservableVariable<LensOverrides['overrides']>(
internalApi.overrides$
);
const [disableTriggers$, disabledTriggersComparator] = buildObservableVariable<
boolean | undefined
>(internalApi.disableTriggers$);

return {
api: {
Expand Down Expand Up @@ -131,7 +140,10 @@ export function initializeDashboardServices(
return attributeService.extractReferences(byValueRuntimeState);
},
},
serialize: () => {
anyStateChange$: merge(internalApi.overrides$, internalApi.disableTriggers$).pipe(
map(() => undefined)
),
getLatestState: () => {
const { style, className } = apiHasLensComponentProps(parentApi)
? parentApi
: ({} as LensComponentProps);
Expand All @@ -143,34 +155,18 @@ export function initializeDashboardServices(
}
: {};
return {
...titleManager.serialize(),
...titleManager.getLatestState(),
style,
className,
...settings,
palette: initialState.palette,
overrides: overrides$.getValue(),
disableTriggers: disableTriggers$.getValue(),
overrides: internalApi.overrides$.getValue(),
disableTriggers: internalApi.disableTriggers$.getValue(),
};
},
comparators: {
...titleManager.comparators,
id: getUnchangingComparator<SerializedTitles & LensPanelProps, 'id'>(),
palette: getUnchangingComparator<SerializedTitles & LensPanelProps, 'palette'>(),
renderMode: getUnchangingComparator<SerializedTitles & LensPanelProps, 'renderMode'>(),
syncColors: getUnchangingComparator<SerializedTitles & LensPanelProps, 'syncColors'>(),
syncCursor: getUnchangingComparator<SerializedTitles & LensPanelProps, 'syncCursor'>(),
syncTooltips: getUnchangingComparator<SerializedTitles & LensPanelProps, 'syncTooltips'>(),
executionContext: getUnchangingComparator<LensSharedProps, 'executionContext'>(),
noPadding: getUnchangingComparator<LensSharedProps, 'noPadding'>(),
viewMode: getUnchangingComparator<LensSharedProps, 'viewMode'>(),
style: getUnchangingComparator<LensSharedProps, 'style'>(),
className: getUnchangingComparator<LensSharedProps, 'className'>(),
overrides: overridesComparator,
disableTriggers: disabledTriggersComparator,
forceDSL: getUnchangingComparator<LensSharedProps, 'forceDSL'>(),
isNewPanel: getUnchangingComparator<{ isNewPanel?: boolean }, 'isNewPanel'>(),
parentApi: getUnchangingComparator<Pick<LensApi, 'parentApi'>, 'parentApi'>(),
reinitializeState: (lastSaved?: LensSerializedState) => {
internalApi.updateDisabledTriggers(lastSaved?.disableTriggers);
internalApi.updateOverrides(lastSaved?.overrides);
},
cleanup: noop,
};
}
Loading