Skip to content

Commit

Permalink
[Embeddable rebuild] Add panel placement registry (elastic#182120)
Browse files Browse the repository at this point in the history
Fixes elastic#182113

## Summary

Adds a registry for plugins to specify the width, height, and placement
strategy for their embeddables.

To test this: 
1. Run `yarn start --run-examples`
2. Load the Kibana sample data logs dataset
3. Start editing the [Logs] Web Traffic dashboard
4. In the "Add panel" dropdown, select the "Field list" subitem under
the "Embeddable examples" group

This also adds some additional documentation to the "Embeddables"
Developer examples.
  • Loading branch information
nickpeihl authored May 2, 2024
1 parent e27066d commit 83ac342
Show file tree
Hide file tree
Showing 19 changed files with 295 additions and 127 deletions.
2 changes: 1 addition & 1 deletion examples/embeddable_examples/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
"developerExamples",
"dataViewFieldEditor"
],
"requiredBundles": ["presentationUtil", "kibanaUtils", "kibanaReact"]
"requiredBundles": ["dashboard", "presentationUtil", "kibanaUtils", "kibanaReact"]
}
}
50 changes: 48 additions & 2 deletions examples/embeddable_examples/public/app/register_embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,24 @@ import { EuiCodeBlock, EuiSpacer, EuiText } from '@elastic/eui';
import registerSearchEmbeddableSource from '!!raw-loader!../react_embeddables/search/register_search_embeddable';
// @ts-ignore
import registerAttachActionSource from '!!raw-loader!../react_embeddables/search/register_add_search_panel_action';
// @ts-ignore
import registerFieldListEmbeddableSource from '!!raw-loader!../react_embeddables/field_list/register_field_list_embeddable';
// @ts-ignore
import registerReactEmbeddableSavedObjectSource from '!!raw-loader!../react_embeddables/register_saved_object_example';

export const RegisterEmbeddable = () => {
return (
<>
<EuiText>
<h2>Register a new embeddable type</h2>
<p>
This plugin registers several embeddable types with{' '}
<strong>registerReactEmbeddableFactory</strong> during plugin start. The code example
below shows Search embeddable registration. Notice how the embeddable factory is imported
asynchronously to limit initial page load size.
</p>
</EuiText>

<EuiSpacer size="s" />
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{registerSearchEmbeddableSource}
</EuiCodeBlock>
Expand All @@ -36,17 +41,58 @@ export const RegisterEmbeddable = () => {
Run the example embeddables by creating a dashboard, clicking <em>Add panel</em> button,
and then selecting <em>Embeddable examples</em> group.
</p>
</EuiText>

<EuiSpacer size="l" />

<EuiText>
<h2>Show embeddables in the Add panel menu</h2>
<p>
Add your own embeddables to <em>Add panel</em> menu by attaching an action to the{' '}
<strong>ADD_PANEL_TRIGGER</strong> trigger. Notice usage of <strong>grouping</strong> to
nest related panel types and avoid bloating <em>Add panel</em> menu. Please reach out to
@elastic/kibana-presentation team to coordinate menu updates.
</p>
</EuiText>

<EuiSpacer size="s" />
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{registerAttachActionSource}
</EuiCodeBlock>

<EuiSpacer size="l" />

<EuiText>
<h2>Configure initial dashboard placement (optional)</h2>
<p>
Add an entry to <strong>registerDashboardPanelPlacementSetting</strong> to configure
initial dashboard placement. Panel placement lets you configure the width, height, and
placement strategy when panels get added to a dashboard. In the example below, the Field
List embeddable will be added to dashboards as a narrow and tall panel.
</p>
</EuiText>
<EuiSpacer size="s" />

<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{registerFieldListEmbeddableSource}
</EuiCodeBlock>

<EuiSpacer size="l" />

<EuiText>
<h2>Saved object embeddables</h2>
<p>
Embeddable factories, such as Lens, Maps, Links, that can reference saved objects should
register their saved object types using{' '}
<strong>registerReactEmbeddableSavedObject</strong>. The <em>Add from library</em> flyout
on Dashboards uses this registry to list saved objects. The example function below could
be called from the public start contract for a plugin.
</p>
</EuiText>
<EuiSpacer size="s" />

<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{registerReactEmbeddableSavedObjectSource}
</EuiCodeBlock>
</>
);
};
4 changes: 3 additions & 1 deletion examples/embeddable_examples/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import { DATA_TABLE_ID } from './react_embeddables/data_table/constants';
import { registerCreateDataTableAction } from './react_embeddables/data_table/create_data_table_action';
import { EUI_MARKDOWN_ID } from './react_embeddables/eui_markdown/constants';
import { registerCreateEuiMarkdownAction } from './react_embeddables/eui_markdown/create_eui_markdown_action';
import { FIELD_LIST_ID } from './react_embeddables/field_list/constants';
import { registerCreateFieldListAction } from './react_embeddables/field_list/create_field_list_action';
import { FIELD_LIST_ID } from './react_embeddables/field_list/constants';
import { registerFieldListPanelPlacementSetting } from './react_embeddables/field_list/register_field_list_embeddable';
import { registerAddSearchPanelAction } from './react_embeddables/search/register_add_search_panel_action';
import { registerSearchEmbeddable } from './react_embeddables/search/register_search_embeddable';

Expand Down Expand Up @@ -58,6 +59,7 @@ export class EmbeddableExamplesPlugin implements Plugin<void, void, SetupDeps, S
);
return getFieldListFactory(core, deps);
});
registerFieldListPanelPlacementSetting();

registerCreateEuiMarkdownAction(deps.uiActions);
registerReactEmbeddableFactory(EUI_MARKDOWN_ID, async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,11 @@

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { css } from '@emotion/react';
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import { Reference } from '@kbn/content-management-utils';
import { CoreStart } from '@kbn/core-lifecycle-browser';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataView } from '@kbn/data-views-plugin/common';
import {
DataViewsPublicPluginStart,
DATA_VIEW_SAVED_OBJECT_TYPE,
} from '@kbn/data-views-plugin/public';
import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/public';
import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { i18n } from '@kbn/i18n';
import { initializeTitles, useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import { LazyDataViewPicker, withSuspense } from '@kbn/presentation-util-plugin/public';
Expand All @@ -31,7 +25,7 @@ import { cloneDeep } from 'lodash';
import React, { useEffect } from 'react';
import { BehaviorSubject, skip, Subscription, switchMap } from 'rxjs';
import { FIELD_LIST_DATA_VIEW_REF_NAME, FIELD_LIST_ID } from './constants';
import { FieldListApi, FieldListSerializedStateState } from './types';
import { FieldListApi, Services, FieldListSerializedStateState } from './types';

const DataViewPicker = withSuspense(LazyDataViewPicker, null);

Expand All @@ -48,17 +42,7 @@ const getCreationOptions: UnifiedFieldListSidebarContainerProps['getCreationOpti

export const getFieldListFactory = (
core: CoreStart,
{
dataViews,
data,
charts,
fieldFormats,
}: {
dataViews: DataViewsPublicPluginStart;
data: DataPublicPluginStart;
charts: ChartsPluginStart;
fieldFormats: FieldFormatsStart;
}
{ dataViews, data, charts, fieldFormats }: Services
) => {
const fieldListEmbeddableFactory: ReactEmbeddableFactory<
FieldListSerializedStateState,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 {
registerDashboardPanelPlacementSetting,
PanelPlacementStrategy,
} from '@kbn/dashboard-plugin/public';
import { FIELD_LIST_ID } from './constants';
import { FieldListSerializedStateState } from './types';

const getPanelPlacementSetting = (serializedState?: FieldListSerializedStateState) => {
// Consider using the serialized state to determine the width, height, and strategy
return {
width: 12,
height: 36,
strategy: PanelPlacementStrategy.placeAtTop,
};
};

export function registerFieldListPanelPlacementSetting() {
registerDashboardPanelPlacementSetting(FIELD_LIST_ID, getPanelPlacementSetting);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
* Side Public License, v 1.
*/

import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { PublishesDataViews, SerializedTitles } from '@kbn/presentation-publishing';
import { PublishesSelectedFields } from './publishes_selected_fields';

Expand All @@ -16,3 +20,10 @@ export type FieldListSerializedStateState = SerializedTitles & {
};

export type FieldListApi = DefaultEmbeddableApi & PublishesSelectedFields & PublishesDataViews;

export interface Services {
dataViews: DataViewsPublicPluginStart;
data: DataPublicPluginStart;
charts: ChartsPluginStart;
fieldFormats: FieldFormatsStart;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 { registerReactEmbeddableSavedObject } from '@kbn/embeddable-plugin/public';

const MY_EMBEDDABLE_TYPE = 'myEmbeddableType';
const MY_SAVED_OBJECT_TYPE = 'mySavedObjectType';
const APP_ICON = 'logoKibana';

export const registerMyEmbeddableSavedObject = () =>
registerReactEmbeddableSavedObject({
onAdd: (container, savedObject) => {
container.addNewPanel({
panelType: MY_EMBEDDABLE_TYPE,
initialState: savedObject.attributes,
});
},
embeddableType: MY_EMBEDDABLE_TYPE,
savedObjectType: MY_SAVED_OBJECT_TYPE,
savedObjectName: 'Some saved object',
getIconForSavedObject: () => APP_ICON,
});
1 change: 1 addition & 0 deletions examples/embeddable_examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"kbn_references": [
"@kbn/core",
"@kbn/ui-actions-plugin",
"@kbn/dashboard-plugin",
"@kbn/embeddable-plugin",
"@kbn/presentation-publishing",
"@kbn/ui-theme",
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/dashboard/public/dashboard_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2;

export const CHANGE_CHECK_DEBOUNCE = 100;

export enum PanelPlacementStrategy {
/** Place on the very top of the Dashboard, add the height of this panel to all other panels. */
placeAtTop = 'placeAtTop',
/** Look for the smallest y and x value where the default panel will fit. */
findTopLeftMostOpenSpace = 'findTopLeftMostOpenSpace',
}

// ------------------------------------------------------------------
// Content Management
// ------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,16 @@ export const backupServiceStrings = {
values: { message },
}),
};

export const panelPlacementStrings = {
getUnknownStrategyError: (strategy: string) =>
i18n.translate('dashboard.panelPlacement.unknownStrategyError', {
defaultMessage: 'Unknown panel placement strategy: {strategy}',
values: { strategy },
}),
getPanelPlacementSettingsExistsError: (panelType: string) =>
i18n.translate('dashboard.panelPlacement.panelPlacementSettingsExistsError', {
defaultMessage: 'Panel placement settings for embeddable type {panelType} already exists',
values: { panelType },
}),
};
Loading

0 comments on commit 83ac342

Please sign in to comment.