mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Embeddables Rebuild] Create & copy panels with runtime state (#188039)
Makes the new Embeddable framework use runtime state for creating new panels, and for passing panel state around in the state transfer service.
This commit is contained in:
parent
31e0c57b82
commit
1140cae77e
15 changed files with 137 additions and 161 deletions
|
@ -21,11 +21,7 @@ import {
|
|||
import { ADD_SAVED_BOOK_ACTION_ID, SAVED_BOOK_ID } from './constants';
|
||||
import { openSavedBookEditor } from './saved_book_editor';
|
||||
import { saveBookAttributes } from './saved_book_library';
|
||||
import {
|
||||
BookByReferenceSerializedState,
|
||||
BookByValueSerializedState,
|
||||
BookSerializedState,
|
||||
} from './types';
|
||||
import { BookRuntimeState } from './types';
|
||||
|
||||
export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, core: CoreStart) => {
|
||||
uiActions.registerAction<EmbeddableApiContext>({
|
||||
|
@ -43,21 +39,17 @@ export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, c
|
|||
parentApi: embeddable,
|
||||
});
|
||||
|
||||
const initialState: BookSerializedState = await (async () => {
|
||||
const initialState: BookRuntimeState = await (async () => {
|
||||
const bookAttributes = serializeBookAttributes(newPanelStateManager);
|
||||
// if we're adding this to the library, we only need to return the by reference state.
|
||||
if (addToLibrary) {
|
||||
const savedBookId = await saveBookAttributes(
|
||||
undefined,
|
||||
serializeBookAttributes(newPanelStateManager)
|
||||
);
|
||||
return { savedBookId } as BookByReferenceSerializedState;
|
||||
const savedBookId = await saveBookAttributes(undefined, bookAttributes);
|
||||
return { savedBookId, ...bookAttributes };
|
||||
}
|
||||
return {
|
||||
attributes: serializeBookAttributes(newPanelStateManager),
|
||||
} as BookByValueSerializedState;
|
||||
return bookAttributes;
|
||||
})();
|
||||
|
||||
embeddable.addNewPanel<BookSerializedState>({
|
||||
embeddable.addNewPanel<BookRuntimeState>({
|
||||
panelType: SAVED_BOOK_ID,
|
||||
initialState,
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ export const runComparators = <StateType extends object = object>(
|
|||
lastSavedState: StateType | undefined,
|
||||
latestState: Partial<StateType>
|
||||
) => {
|
||||
if (!lastSavedState) {
|
||||
if (!lastSavedState || Object.keys(latestState).length === 0) {
|
||||
// if we have no last saved state, everything is considered a change
|
||||
return latestState;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { omit } from 'lodash';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
|
@ -20,8 +17,10 @@ import {
|
|||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { EmbeddablePackageState, PanelNotFoundError } from '@kbn/embeddable-plugin/public';
|
||||
import { apiHasSnapshottableState } from '@kbn/presentation-containers/interfaces/serialized_state';
|
||||
import { LazyDashboardPicker, withSuspense } from '@kbn/presentation-util-plugin/public';
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { createDashboardEditUrl, CREATE_NEW_DASHBOARD_URL } from '../dashboard_constants';
|
||||
import { pluginServices } from '../services/plugin_services';
|
||||
import { CopyToDashboardAPI } from './copy_to_dashboard_action';
|
||||
|
@ -51,21 +50,21 @@ export function CopyToDashboardModal({ api, closeModal }: CopyToDashboardModalPr
|
|||
const onSubmit = useCallback(async () => {
|
||||
const dashboard = api.parentApi;
|
||||
const panelToCopy = await dashboard.getDashboardPanelFromId(api.uuid);
|
||||
const runtimeSnapshot = apiHasSnapshottableState(api) ? api.snapshotRuntimeState() : undefined;
|
||||
|
||||
if (!panelToCopy) {
|
||||
if (!panelToCopy && !runtimeSnapshot) {
|
||||
throw new PanelNotFoundError();
|
||||
}
|
||||
|
||||
const state: EmbeddablePackageState = {
|
||||
type: panelToCopy.type,
|
||||
input: {
|
||||
input: runtimeSnapshot ?? {
|
||||
...omit(panelToCopy.explicitInput, 'id'),
|
||||
},
|
||||
size: {
|
||||
width: panelToCopy.gridData.w,
|
||||
height: panelToCopy.gridData.h,
|
||||
},
|
||||
references: panelToCopy.references,
|
||||
};
|
||||
|
||||
const path =
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import {
|
||||
ControlGroupInput,
|
||||
CONTROL_GROUP_TYPE,
|
||||
|
@ -28,16 +27,21 @@ import {
|
|||
TimeRange,
|
||||
} from '@kbn/es-query';
|
||||
import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { cloneDeep, identity, omit, pickBy } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
|
||||
import { map, distinctUntilChanged, startWith } from 'rxjs';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
startWith,
|
||||
Subject,
|
||||
} from 'rxjs';
|
||||
import { v4 } from 'uuid';
|
||||
import { combineDashboardFiltersWithControlGroupFilters } from './controls/dashboard_control_group_integration';
|
||||
import {
|
||||
DashboardContainerInput,
|
||||
DashboardPanelMap,
|
||||
DashboardPanelState,
|
||||
prefixReferencesFromPanel,
|
||||
} from '../../../../common';
|
||||
import {
|
||||
DEFAULT_DASHBOARD_INPUT,
|
||||
|
@ -56,11 +60,14 @@ import { startDiffingDashboardState } from '../../state/diffing/dashboard_diffin
|
|||
import { DashboardPublicState, UnsavedPanelState } from '../../types';
|
||||
import { DashboardContainer } from '../dashboard_container';
|
||||
import { DashboardCreationOptions } from '../dashboard_container_factory';
|
||||
import { startSyncingDashboardControlGroup } from './controls/dashboard_control_group_integration';
|
||||
import {
|
||||
combineDashboardFiltersWithControlGroupFilters,
|
||||
startSyncingDashboardControlGroup,
|
||||
} from './controls/dashboard_control_group_integration';
|
||||
import { startSyncingDashboardDataViews } from './data_views/sync_dashboard_data_views';
|
||||
import { startQueryPerformanceTracking } from './performance/query_performance_tracking';
|
||||
import { startDashboardSearchSessionIntegration } from './search_sessions/start_dashboard_search_session_integration';
|
||||
import { syncUnifiedSearchState } from './unified_search/sync_dashboard_unified_search_state';
|
||||
import { startQueryPerformanceTracking } from './performance/query_performance_tracking';
|
||||
|
||||
/**
|
||||
* Builds a new Dashboard from scratch.
|
||||
|
@ -276,17 +283,6 @@ export const initializeDashboard = async ({
|
|||
...overrideInput,
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Set restored runtime state for react embeddables.
|
||||
// --------------------------------------------------------------------------------------
|
||||
untilDashboardReady().then((dashboardContainer) => {
|
||||
for (const idWithRuntimeState of Object.keys(runtimePanelsToRestore)) {
|
||||
const restoredRuntimeStateForChild = runtimePanelsToRestore[idWithRuntimeState];
|
||||
if (!restoredRuntimeStateForChild) continue;
|
||||
dashboardContainer.setRuntimeStateForChild(idWithRuntimeState, restoredRuntimeStateForChild);
|
||||
}
|
||||
});
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Combine input from saved object, session storage, & passed input to create initial input.
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -391,7 +387,7 @@ export const initializeDashboard = async ({
|
|||
const sameType = panelToUpdate.type === incomingEmbeddable.type;
|
||||
|
||||
panelToUpdate.type = incomingEmbeddable.type;
|
||||
panelToUpdate.explicitInput = {
|
||||
const nextRuntimeState = {
|
||||
// if the incoming panel is the same type as what was there before we can safely spread the old panel's explicit input
|
||||
...(sameType ? panelToUpdate.explicitInput : {}),
|
||||
|
||||
|
@ -401,6 +397,13 @@ export const initializeDashboard = async ({
|
|||
// maintain hide panel titles setting.
|
||||
hidePanelTitles: panelToUpdate.explicitInput.hidePanelTitles,
|
||||
};
|
||||
if (reactEmbeddableRegistryHasKey(incomingEmbeddable.type)) {
|
||||
panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id };
|
||||
runtimePanelsToRestore[incomingEmbeddable.embeddableId] = nextRuntimeState;
|
||||
} else {
|
||||
panelToUpdate.explicitInput = nextRuntimeState;
|
||||
}
|
||||
|
||||
untilDashboardReady().then((container) =>
|
||||
scrolltoIncomingEmbeddable(container, incomingEmbeddable.embeddableId as string)
|
||||
);
|
||||
|
@ -429,19 +432,27 @@ export const initializeDashboard = async ({
|
|||
currentPanels,
|
||||
}
|
||||
);
|
||||
const newPanelState: DashboardPanelState = {
|
||||
explicitInput: { ...incomingEmbeddable.input, id: embeddableId },
|
||||
type: incomingEmbeddable.type,
|
||||
gridData: {
|
||||
...newPanelPlacement,
|
||||
i: embeddableId,
|
||||
},
|
||||
};
|
||||
if (incomingEmbeddable.references) {
|
||||
container.savedObjectReferences.push(
|
||||
...prefixReferencesFromPanel(embeddableId, incomingEmbeddable.references)
|
||||
);
|
||||
}
|
||||
const newPanelState: DashboardPanelState = (() => {
|
||||
if (reactEmbeddableRegistryHasKey(incomingEmbeddable.type)) {
|
||||
runtimePanelsToRestore[embeddableId] = incomingEmbeddable.input;
|
||||
return {
|
||||
explicitInput: { id: embeddableId },
|
||||
type: incomingEmbeddable.type,
|
||||
gridData: {
|
||||
...newPanelPlacement,
|
||||
i: embeddableId,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
explicitInput: { ...incomingEmbeddable.input, id: embeddableId },
|
||||
type: incomingEmbeddable.type,
|
||||
gridData: {
|
||||
...newPanelPlacement,
|
||||
i: embeddableId,
|
||||
},
|
||||
};
|
||||
})();
|
||||
container.updateInput({
|
||||
panels: {
|
||||
...container.getInput().panels,
|
||||
|
@ -458,6 +469,17 @@ export const initializeDashboard = async ({
|
|||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Set restored runtime state for react embeddables.
|
||||
// --------------------------------------------------------------------------------------
|
||||
untilDashboardReady().then((dashboardContainer) => {
|
||||
for (const idWithRuntimeState of Object.keys(runtimePanelsToRestore)) {
|
||||
const restoredRuntimeStateForChild = runtimePanelsToRestore[idWithRuntimeState];
|
||||
if (!restoredRuntimeStateForChild) continue;
|
||||
dashboardContainer.setRuntimeStateForChild(idWithRuntimeState, restoredRuntimeStateForChild);
|
||||
}
|
||||
});
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Start the control group integration.
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
|
|
@ -527,10 +527,12 @@ export class DashboardContainer
|
|||
i: newId,
|
||||
},
|
||||
explicitInput: {
|
||||
...panelPackage.initialState,
|
||||
id: newId,
|
||||
},
|
||||
};
|
||||
if (panelPackage.initialState) {
|
||||
this.setRuntimeStateForChild(newId, panelPackage.initialState);
|
||||
}
|
||||
this.updateInput({ panels: { ...otherPanels, [newId]: newPanel } });
|
||||
onSuccess(newId, newPanel.explicitInput.title);
|
||||
return await this.untilReactEmbeddableLoaded<ApiType>(newId);
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Reference } from '@kbn/content-management-utils';
|
||||
|
||||
export const EMBEDDABLE_EDITOR_STATE_KEY = 'embeddable_editor_state';
|
||||
|
||||
/**
|
||||
|
@ -39,23 +37,15 @@ export const EMBEDDABLE_PACKAGE_STATE_KEY = 'embeddable_package_state';
|
|||
*/
|
||||
export interface EmbeddablePackageState {
|
||||
type: string;
|
||||
/**
|
||||
* For react embeddables, this input must be runtime state.
|
||||
*/
|
||||
input: object;
|
||||
embeddableId?: string;
|
||||
size?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
/**
|
||||
* Copy dashboard panel transfers serialized panel state for React embeddables.
|
||||
* The rawState will be passed into the input and references are passed separately
|
||||
* so the container can update its references array and the updated references
|
||||
* are correctly passed to the factory's deserialize method.
|
||||
*
|
||||
* Legacy embeddables have already injected the references
|
||||
* into the input state, so they will not pass references.
|
||||
*/
|
||||
references?: Reference[];
|
||||
|
||||
/**
|
||||
* Pass current search session id when navigating to an editor,
|
||||
* Editors could use it continue previous search session
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
"@kbn/presentation-containers",
|
||||
"@kbn/react-kibana-mount",
|
||||
"@kbn/analytics",
|
||||
"@kbn/content-management-utils"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@ import { ADD_PANEL_TRIGGER, IncompatibleActionError } from '@kbn/ui-actions-plug
|
|||
import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import { APP_ICON, APP_NAME, CONTENT_ID } from '../../common';
|
||||
import { uiActions } from '../services/kibana_services';
|
||||
import { serializeLinksAttributes } from '../lib/serialize_attributes';
|
||||
import { LinksSerializedState } from '../types';
|
||||
|
||||
const ADD_LINKS_PANEL_ACTION_ID = 'create_links_panel';
|
||||
|
||||
|
@ -35,14 +33,9 @@ export const registerCreateLinksPanelAction = () => {
|
|||
});
|
||||
if (!runtimeState) return;
|
||||
|
||||
const initialState: LinksSerializedState = runtimeState.savedObjectId
|
||||
? { savedObjectId: runtimeState.savedObjectId }
|
||||
: // We should not extract the references when passing initialState to addNewPanel
|
||||
serializeLinksAttributes(runtimeState, false);
|
||||
|
||||
await embeddable.addNewPanel({
|
||||
panelType: CONTENT_ID,
|
||||
initialState,
|
||||
initialState: runtimeState,
|
||||
});
|
||||
},
|
||||
grouping: [COMMON_EMBEDDABLE_GROUPING.annotation],
|
||||
|
|
|
@ -9,4 +9,3 @@
|
|||
export { linksClient } from './links_content_management_client';
|
||||
export { checkForDuplicateTitle } from './duplicate_title_check';
|
||||
export { runSaveToLibrary } from './save_to_library';
|
||||
export { loadFromLibrary } from './load_from_library';
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* 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 { injectReferences } from '../../common/persistable_state';
|
||||
import { linksClient } from './links_content_management_client';
|
||||
|
||||
export async function loadFromLibrary(savedObjectId: string) {
|
||||
const {
|
||||
item: savedObject,
|
||||
meta: { outcome, aliasPurpose, aliasTargetId },
|
||||
} = await linksClient.get(savedObjectId);
|
||||
if (savedObject.error) throw savedObject.error;
|
||||
const { attributes } = injectReferences(savedObject);
|
||||
return {
|
||||
attributes,
|
||||
metaInfo: {
|
||||
sharingSavedObjectProps: {
|
||||
aliasTargetId,
|
||||
outcome,
|
||||
aliasPurpose,
|
||||
sourceId: savedObjectId,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
|
@ -24,7 +24,7 @@ import {
|
|||
import { linksClient } from '../content_management';
|
||||
import { getMockLinksParentApi } from '../mocks';
|
||||
|
||||
const links: Link[] = [
|
||||
const getLinks: () => Link[] = () => [
|
||||
{
|
||||
id: '001',
|
||||
order: 0,
|
||||
|
@ -54,7 +54,7 @@ const links: Link[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const resolvedLinks: ResolvedLink[] = [
|
||||
const getResolvedLinks: () => ResolvedLink[] = () => [
|
||||
{
|
||||
id: '001',
|
||||
order: 0,
|
||||
|
@ -105,33 +105,35 @@ const references = [
|
|||
|
||||
jest.mock('../lib/resolve_links', () => {
|
||||
return {
|
||||
resolveLinks: jest.fn().mockResolvedValue(resolvedLinks),
|
||||
resolveLinks: jest.fn().mockResolvedValue(getResolvedLinks()),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../content_management', () => {
|
||||
return {
|
||||
loadFromLibrary: jest.fn((savedObjectId) => {
|
||||
return Promise.resolve({
|
||||
attributes: {
|
||||
title: 'links 001',
|
||||
description: 'some links',
|
||||
links,
|
||||
layout: 'vertical',
|
||||
},
|
||||
metaInfo: {
|
||||
sharingSavedObjectProps: {
|
||||
linksClient: {
|
||||
create: jest.fn().mockResolvedValue({ item: { id: '333' } }),
|
||||
update: jest.fn().mockResolvedValue({ item: { id: '123' } }),
|
||||
get: jest.fn((savedObjectId) => {
|
||||
return Promise.resolve({
|
||||
item: {
|
||||
id: savedObjectId,
|
||||
attributes: {
|
||||
title: 'links 001',
|
||||
description: 'some links',
|
||||
links: getLinks(),
|
||||
layout: 'vertical',
|
||||
},
|
||||
references,
|
||||
},
|
||||
meta: {
|
||||
aliasTargetId: '123',
|
||||
outcome: 'exactMatch',
|
||||
aliasPurpose: 'sharing',
|
||||
sourceId: savedObjectId,
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
linksClient: {
|
||||
create: jest.fn().mockResolvedValue({ item: { id: '333' } }),
|
||||
update: jest.fn().mockResolvedValue({ item: { id: '123' } }),
|
||||
});
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -157,7 +159,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
defaultPanelTitle: 'links 001',
|
||||
defaultPanelDescription: 'some links',
|
||||
layout: 'vertical',
|
||||
links: resolvedLinks,
|
||||
links: getResolvedLinks(),
|
||||
description: 'just a few links',
|
||||
title: 'my links',
|
||||
savedObjectId: '123',
|
||||
|
@ -172,7 +174,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
test('deserializeState', async () => {
|
||||
const deserializedState = await factory.deserializeState({
|
||||
rawState,
|
||||
references,
|
||||
references: [], // no references passed because the panel is by reference
|
||||
});
|
||||
expect(deserializedState).toEqual({
|
||||
...expectedRuntimeState,
|
||||
|
@ -240,7 +242,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
attributes: {
|
||||
description: 'some links',
|
||||
title: 'links 001',
|
||||
links,
|
||||
links: getLinks(),
|
||||
layout: 'vertical',
|
||||
},
|
||||
},
|
||||
|
@ -254,7 +256,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
describe('by value embeddable', () => {
|
||||
const rawState = {
|
||||
attributes: {
|
||||
links,
|
||||
links: getLinks(),
|
||||
layout: 'horizontal',
|
||||
},
|
||||
description: 'just a few links',
|
||||
|
@ -265,7 +267,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
defaultPanelTitle: undefined,
|
||||
defaultPanelDescription: undefined,
|
||||
layout: 'horizontal',
|
||||
links: resolvedLinks,
|
||||
links: getResolvedLinks(),
|
||||
description: 'just a few links',
|
||||
title: 'my links',
|
||||
savedObjectId: undefined,
|
||||
|
@ -315,7 +317,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
description: 'just a few links',
|
||||
hidePanelTitles: undefined,
|
||||
attributes: {
|
||||
links,
|
||||
links: getLinks(),
|
||||
layout: 'horizontal',
|
||||
},
|
||||
},
|
||||
|
@ -342,7 +344,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
expect(linksClient.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
title: 'some new title',
|
||||
links,
|
||||
links: getLinks(),
|
||||
layout: 'horizontal',
|
||||
},
|
||||
options: { references },
|
||||
|
|
|
@ -74,9 +74,10 @@ export const getLinksEmbeddableFactory = () => {
|
|||
const { title, description } = serializedState.rawState;
|
||||
|
||||
if (linksSerializeStateIsByReference(state)) {
|
||||
const attributes = await deserializeLinksSavedObject(state);
|
||||
const linksSavedObject = await linksClient.get(state.savedObjectId);
|
||||
const runtimeState = await deserializeLinksSavedObject(linksSavedObject.item);
|
||||
return {
|
||||
...attributes,
|
||||
...runtimeState,
|
||||
title,
|
||||
description,
|
||||
};
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { loadFromLibrary } from '../content_management';
|
||||
import { LinksByReferenceSerializedState, LinksSerializedState } from '../types';
|
||||
import { SOWithMetadata } from '@kbn/content-management-utils';
|
||||
import { LinksAttributes } from '../../common/content_management';
|
||||
import { injectReferences } from '../../common/persistable_state';
|
||||
import { LinksByReferenceSerializedState, LinksRuntimeState, LinksSerializedState } from '../types';
|
||||
import { resolveLinks } from './resolve_links';
|
||||
|
||||
export const linksSerializeStateIsByReference = (
|
||||
|
@ -16,8 +18,11 @@ export const linksSerializeStateIsByReference = (
|
|||
return Boolean(state && (state as LinksByReferenceSerializedState).savedObjectId !== undefined);
|
||||
};
|
||||
|
||||
export const deserializeLinksSavedObject = async (state: LinksByReferenceSerializedState) => {
|
||||
const { attributes } = await loadFromLibrary(state.savedObjectId);
|
||||
export const deserializeLinksSavedObject = async (
|
||||
linksSavedObject: SOWithMetadata<LinksAttributes>
|
||||
): Promise<LinksRuntimeState> => {
|
||||
if (linksSavedObject.error) throw linksSavedObject.error;
|
||||
const { attributes } = injectReferences(linksSavedObject);
|
||||
|
||||
const links = await resolveLinks(attributes.links ?? []);
|
||||
|
||||
|
@ -26,7 +31,7 @@ export const deserializeLinksSavedObject = async (state: LinksByReferenceSeriali
|
|||
return {
|
||||
links,
|
||||
layout,
|
||||
savedObjectId: state.savedObjectId,
|
||||
savedObjectId: linksSavedObject.id,
|
||||
defaultPanelTitle,
|
||||
defaultPanelDescription,
|
||||
};
|
||||
|
|
|
@ -14,9 +14,15 @@ export const serializeLinksAttributes = (
|
|||
state: LinksRuntimeState,
|
||||
shouldExtractReferences: boolean = true
|
||||
) => {
|
||||
const linksToSave: Link[] | undefined = state.links?.map(
|
||||
({ title, description, error, ...linkToSave }) => linkToSave
|
||||
);
|
||||
const linksToSave: Link[] | undefined = state.links
|
||||
?.map(({ title, description, error, ...linkToSave }) => linkToSave)
|
||||
?.map(
|
||||
// fiilter out null values which may have been introduced by the session state backup (undefined values are serialized as null).
|
||||
(link) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(link).filter(([key, value]) => value !== null)
|
||||
) as unknown as Link
|
||||
);
|
||||
const attributes = {
|
||||
title: state.defaultPanelTitle,
|
||||
description: state.defaultPanelDescription,
|
||||
|
|
|
@ -22,17 +22,14 @@ import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
|||
import { VisualizationsSetup } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
|
||||
import { LinksSerializedState } from './types';
|
||||
import { LinksRuntimeState } from './types';
|
||||
import { APP_ICON, APP_NAME, CONTENT_ID, LATEST_VERSION } from '../common';
|
||||
import { LinksCrudTypes } from '../common/content_management';
|
||||
import { LinksStrings } from './components/links_strings';
|
||||
import { getLinksClient } from './content_management/links_content_management_client';
|
||||
import { setKibanaServices, untilPluginStartServicesReady } from './services/kibana_services';
|
||||
import { registerCreateLinksPanelAction } from './actions/create_links_panel_action';
|
||||
import {
|
||||
deserializeLinksSavedObject,
|
||||
linksSerializeStateIsByReference,
|
||||
} from './lib/deserialize_from_library';
|
||||
import { deserializeLinksSavedObject } from './lib/deserialize_from_library';
|
||||
export interface LinksSetupDependencies {
|
||||
embeddable: EmbeddableSetup;
|
||||
visualizations: VisualizationsSetup;
|
||||
|
@ -64,10 +61,11 @@ export class LinksPlugin
|
|||
});
|
||||
|
||||
plugins.embeddable.registerReactEmbeddableSavedObject({
|
||||
onAdd: (container, savedObject) => {
|
||||
container.addNewPanel({
|
||||
onAdd: async (container, savedObject) => {
|
||||
const initialState = await deserializeLinksSavedObject(savedObject);
|
||||
container.addNewPanel<LinksRuntimeState>({
|
||||
panelType: CONTENT_ID,
|
||||
initialState: { savedObjectId: savedObject.id },
|
||||
initialState,
|
||||
});
|
||||
},
|
||||
embeddableType: CONTENT_ID,
|
||||
|
@ -103,7 +101,8 @@ export class LinksPlugin
|
|||
editor: {
|
||||
onEdit: async (savedObjectId: string) => {
|
||||
const { openEditorFlyout } = await import('./editor/open_editor_flyout');
|
||||
const initialState = await deserializeLinksSavedObject({ savedObjectId });
|
||||
const linksSavedObject = await getLinksClient().get(savedObjectId);
|
||||
const initialState = await deserializeLinksSavedObject(linksSavedObject.item);
|
||||
await openEditorFlyout({ initialState });
|
||||
},
|
||||
},
|
||||
|
@ -128,14 +127,11 @@ export class LinksPlugin
|
|||
|
||||
plugins.dashboard.registerDashboardPanelPlacementSetting(
|
||||
CONTENT_ID,
|
||||
async (serializedState?: LinksSerializedState) => {
|
||||
if (!serializedState) return {};
|
||||
const { links, layout } = linksSerializeStateIsByReference(serializedState)
|
||||
? await deserializeLinksSavedObject(serializedState)
|
||||
: serializedState.attributes;
|
||||
const isHorizontal = layout === 'horizontal';
|
||||
async (runtimeState?: LinksRuntimeState) => {
|
||||
if (!runtimeState) return {};
|
||||
const isHorizontal = runtimeState.layout === 'horizontal';
|
||||
const width = isHorizontal ? DASHBOARD_GRID_COLUMN_COUNT : 8;
|
||||
const height = isHorizontal ? 4 : (links?.length ?? 1 * 3) + 4;
|
||||
const height = isHorizontal ? 4 : (runtimeState.links?.length ?? 1 * 3) + 4;
|
||||
return { width, height, strategy: PanelPlacementStrategy.placeAtTop };
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue